Make entities serializable for DB validation (#1401)

* Make entities serializable for DB validation

Make entities that are asynchronously replicated between Datastore and
Cloud SQL serializable so that they may be used in BEAM pipeline based
comparison tool.

Introduced an UnsafeSerializable interface (extending Serializable) and
added to relevant classes. Implementing classes are allowed some
shortcuts as explained in the interface's Javadoc. Post migration we
will decide whether to revert this change or properly implement
serialization.

Verified with production data.
This commit is contained in:
Weimin Yu 2021-10-28 12:19:09 -04:00 committed by GitHub
parent 0d5b436b41
commit 0978ede5d9
47 changed files with 333 additions and 62 deletions

View file

@ -501,7 +501,8 @@ task javadoc(type: Javadoc) {
options.addBooleanOption('Xdoclint:all,-missing', true)
options.addBooleanOption("-allow-script-in-comments",true)
options.tags = ["type:a:Generic Type",
"error:a:Expected Error"]
"error:a:Expected Error",
"invariant:a:Guaranteed Property"]
}
tasks.build.dependsOn(tasks.javadoc)

View file

@ -31,7 +31,7 @@ import javax.xml.bind.annotation.XmlTransient;
* that we can enforce strictly increasing timestamps.
*/
@MappedSuperclass
public abstract class BackupGroupRoot extends ImmutableObject {
public abstract class BackupGroupRoot extends ImmutableObject implements UnsafeSerializable {
/**
* An automatically managed timestamp of when this object was last written to Datastore.

View file

@ -23,7 +23,7 @@ import org.joda.time.DateTime;
*
* @see CreateAutoTimestampTranslatorFactory
*/
public class CreateAutoTimestamp extends ImmutableObject {
public class CreateAutoTimestamp extends ImmutableObject implements UnsafeSerializable {
DateTime timestamp;

View file

@ -85,6 +85,9 @@ public abstract class ImmutableObject implements Cloneable {
@Target(FIELD)
public @interface Insignificant {}
// Note: if this class is made to implement Serializable, this field must become 'transient' since
// hashing is not stable across executions. Also note that @XmlTransient is forbidden on transient
// fields and need to be removed if transient is added.
@Ignore @XmlTransient protected Integer hashCode;
private boolean equalsImmutableObject(ImmutableObject other) {

View file

@ -0,0 +1,34 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model;
import java.io.Serializable;
/**
* Marker interface for Nomulus entities whose serialization are implemented in a fragile way. These
* entities are made {@link Serializable} so that they can be passed between JVMs. The intended use
* case is BEAM pipeline-based cross-database data validation between Datastore and Cloud SQL during
* the migration. Note that only objects loaded from the SQL database need serialization support.
* Objects exported from Datastore can already be serialized as protocol buffers.
*
* <p>All entities implementing this interface take advantage of the fact that all Java collection
* classes we use, either directly or indirectly, including those in Java libraries, Guava,
* Objectify, and Hibernate are {@code Serializable}.
*
* <p>The {@code serialVersionUID} field has also been omitted in the implementing classes, since
* they are not used for persistence.
*/
// TODO(b/203609782): either remove this interface or fix implementors post migration.
public interface UnsafeSerializable extends Serializable {}

View file

@ -38,7 +38,7 @@ import org.joda.time.DateTime;
* @see UpdateAutoTimestampTranslatorFactory
*/
@Embeddable
public class UpdateAutoTimestamp extends ImmutableObject {
public class UpdateAutoTimestamp extends ImmutableObject implements UnsafeSerializable {
// When set to true, database converters/translators should do the auto update. When set to
// false, auto update should be suspended (this exists to allow us to preserve the original value

View file

@ -39,6 +39,7 @@ import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.TimeOfYear;
import google.registry.model.domain.DomainBase;
@ -72,7 +73,7 @@ import org.joda.time.DateTime;
/** A billable event in a domain's lifecycle. */
@MappedSuperclass
public abstract class BillingEvent extends ImmutableObject
implements Buildable, TransferServerApproveEntity {
implements Buildable, TransferServerApproveEntity, UnsafeSerializable {
/** The reason for the bill, which maps 1:1 to skus in go/registry-billing-skus. */
public enum Reason {

View file

@ -27,13 +27,13 @@ import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.common.Cursor.CursorId;
import google.registry.model.replay.DatastoreAndSqlEntity;
import google.registry.model.tld.Registry;
import google.registry.persistence.VKey;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import javax.persistence.Column;
@ -53,7 +53,7 @@ import org.joda.time.DateTime;
@javax.persistence.Entity
@IdClass(CursorId.class)
@InCrossTld
public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity {
public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity, UnsafeSerializable {
/** The scope of a global cursor. A global cursor is a cursor that is not specific to one tld. */
public static final String GLOBAL = "GLOBAL";
@ -283,7 +283,7 @@ public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity {
return cursorTime;
}
static class CursorId extends ImmutableObject implements Serializable {
public static class CursorId extends ImmutableObject implements UnsafeSerializable {
public CursorType type;
public String scope;

View file

@ -28,6 +28,7 @@ import com.google.common.collect.Range;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Index;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import java.util.List;
import javax.persistence.Embeddable;
import org.joda.time.DateTime;
@ -45,7 +46,7 @@ import org.joda.time.DateTime;
*/
@Embed
@Embeddable
public class TimeOfYear extends ImmutableObject {
public class TimeOfYear extends ImmutableObject implements UnsafeSerializable {
/**
* The time as "month day millis" with all fields left-padded with zeroes so that lexographic

View file

@ -27,7 +27,9 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.googlecode.objectify.mapper.Mapper;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.util.TypeUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@ -53,11 +55,12 @@ import org.joda.time.DateTime;
* to use for storing the list of transitions. The user is given this choice of subclass so that the
* field of the value type stored in the transition can be given a customized name.
*/
public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedTransition<V>>
extends ForwardingMap<DateTime, T> {
public class TimedTransitionProperty<
V extends Serializable, T extends TimedTransitionProperty.TimedTransition<V>>
extends ForwardingMap<DateTime, T> implements UnsafeSerializable {
/**
* A transition to a value of type {@code V} at a certain time. This superclass only has a field
* A transition to a value of type {@code V} at a certain time. This superclass only has a field
* for the {@code DateTime}, which means that subclasses should supply the field of type {@code V}
* and implementations of the abstract getter and setter methods to access that field. This design
* is so that subclasses tagged with @Embed can define a custom field name for their value, for
@ -65,11 +68,12 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
*
* <p>The public visibility of this class exists only so that it can be subclassed; clients should
* never call any methods on this class or attempt to access its members, but should instead treat
* it as a customizable implementation detail of {@code TimedTransitionProperty}. However, note
* it as a customizable implementation detail of {@code TimedTransitionProperty}. However, note
* that subclasses must also have public visibility so that they can be instantiated via
* reflection in a call to {@code fromValueMap}.
*/
public abstract static class TimedTransition<V> extends ImmutableObject {
public abstract static class TimedTransition<V extends Serializable> extends ImmutableObject
implements UnsafeSerializable {
/** The time at which this value becomes the active value. */
private DateTime transitionTime;
@ -89,16 +93,16 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
}
/**
* Converts the provided value map into the equivalent transition map, using transition objects
* of the given TimedTransition subclass. The value map must be sorted according to the natural
* Converts the provided value map into the equivalent transition map, using transition objects of
* the given TimedTransition subclass. The value map must be sorted according to the natural
* ordering of its DateTime keys, and keys cannot be earlier than START_OF_TIME.
*/
// NB: The Class<T> parameter could be eliminated by getting the class via reflection, but then
// the callsite cannot infer T, so unless you explicitly call this as .<V, T>fromValueMap() it
// will default to using just TimedTransition<V>, which fails at runtime.
private static <V, T extends TimedTransition<V>> NavigableMap<DateTime, T> makeTransitionMap(
ImmutableSortedMap<DateTime, V> valueMap,
final Class<T> timedTransitionSubclass) {
private static <V extends Serializable, T extends TimedTransition<V>>
NavigableMap<DateTime, T> makeTransitionMap(
ImmutableSortedMap<DateTime, V> valueMap, final Class<T> timedTransitionSubclass) {
checkArgument(
Ordering.natural().equals(valueMap.comparator()),
"Timed transition value map must have transition time keys in chronological order");
@ -121,9 +125,9 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
*
* <p>This method should be the normal method for constructing a {@link TimedTransitionProperty}.
*/
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> fromValueMap(
ImmutableSortedMap<DateTime, V> valueMap,
final Class<T> timedTransitionSubclass) {
public static <V extends Serializable, T extends TimedTransition<V>>
TimedTransitionProperty<V, T> fromValueMap(
ImmutableSortedMap<DateTime, V> valueMap, final Class<T> timedTransitionSubclass) {
return new TimedTransitionProperty<>(ImmutableSortedMap.copyOf(
makeTransitionMap(valueMap, timedTransitionSubclass)));
}
@ -175,10 +179,10 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
* @param allowedTransitions optional map of all possible state-to-state transitions
* @param allowedTransitionMapName optional transition map description string for error messages
* @param initialValue optional initial value; if present, the first transition must have this
* value
* value
* @param badInitialValueErrorMessage option error message string if the initial value is wrong
*/
public static <V, T extends TimedTransitionProperty.TimedTransition<V>>
public static <V extends Serializable, T extends TimedTransitionProperty.TimedTransition<V>>
TimedTransitionProperty<V, T> make(
ImmutableSortedMap<DateTime, V> newTransitions,
Class<T> transitionClass,
@ -200,7 +204,7 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
* Validates that a transition map is not null or empty, starts at START_OF_TIME, and has
* transitions which move from one value to another in allowed ways.
*/
public static <V, T extends TimedTransitionProperty.TimedTransition<V>>
public static <V extends Serializable, T extends TimedTransitionProperty.TimedTransition<V>>
void validateTimedTransitionMap(
@Nullable NavigableMap<DateTime, V> transitionMap,
ImmutableMultimap<V, V> allowedTransitions,
@ -240,8 +244,9 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
* annotation. The map for those fields must be mutable so that Objectify can load values from
* Datastore into the map, but clients should still never mutate the field's map directly.
*/
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> forMapify(
ImmutableSortedMap<DateTime, V> valueMap, Class<T> timedTransitionSubclass) {
public static <V extends Serializable, T extends TimedTransition<V>>
TimedTransitionProperty<V, T> forMapify(
ImmutableSortedMap<DateTime, V> valueMap, Class<T> timedTransitionSubclass) {
return new TimedTransitionProperty<>(
new TreeMap<>(makeTransitionMap(valueMap, timedTransitionSubclass)));
}
@ -254,8 +259,9 @@ public class TimedTransitionProperty<V, T extends TimedTransitionProperty.TimedT
* annotation. The map for those fields must be mutable so that Objectify can load values from
* Datastore into the map, but clients should still never mutate the field's map directly.
*/
public static <V, T extends TimedTransition<V>> TimedTransitionProperty<V, T> forMapify(
V valueAtStartOfTime, Class<T> timedTransitionSubclass) {
public static <V extends Serializable, T extends TimedTransition<V>>
TimedTransitionProperty<V, T> forMapify(
V valueAtStartOfTime, Class<T> timedTransitionSubclass) {
return forMapify(
ImmutableSortedMap.of(START_OF_TIME, valueAtStartOfTime), timedTransitionSubclass);
}

View file

@ -20,6 +20,7 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.replay.DatastoreEntity;
import google.registry.model.replay.SqlEntity;
@ -59,7 +60,7 @@ import javax.persistence.PostLoad;
@EntitySubclass
@Access(AccessType.FIELD)
@IdClass(ContactHistoryId.class)
public class ContactHistory extends HistoryEntry implements SqlEntity {
public class ContactHistory extends HistoryEntry implements SqlEntity, UnsafeSerializable {
// Store ContactBase instead of ContactResource so we don't pick up its @Id
// Nullable for the sake of pre-Registry-3.0 history objects

View file

@ -20,7 +20,9 @@ import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.eppcommon.PresenceMarker;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
@ -31,7 +33,7 @@ import javax.xml.bind.annotation.XmlType;
@Embed
@Embeddable
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
public class Disclose extends ImmutableObject {
public class Disclose extends ImmutableObject implements UnsafeSerializable {
List<PostalInfoChoice> name;
@ -78,7 +80,7 @@ public class Disclose extends ImmutableObject {
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
@Embed
public static class PostalInfoChoice extends ImmutableObject {
public static class PostalInfoChoice extends ImmutableObject implements Serializable {
@XmlAttribute
PostalInfo.Type type;

View file

@ -20,6 +20,7 @@ import com.googlecode.objectify.annotation.Embed;
import google.registry.model.Buildable;
import google.registry.model.Buildable.Overlayable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import java.util.Optional;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
@ -38,7 +39,8 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@Embed
@Embeddable
@XmlType(propOrder = {"name", "org", "address", "type"})
public class PostalInfo extends ImmutableObject implements Overlayable<PostalInfo> {
public class PostalInfo extends ImmutableObject
implements Overlayable<PostalInfo>, UnsafeSerializable {
/** The type of the address, either localized or international. */
public enum Type {

View file

@ -21,6 +21,7 @@ import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.contact.ContactResource;
import google.registry.persistence.VKey;
import javax.persistence.Embeddable;
@ -46,7 +47,7 @@ import javax.xml.bind.annotation.XmlEnumValue;
*/
@Embed
@Embeddable
public class DesignatedContact extends ImmutableObject {
public class DesignatedContact extends ImmutableObject implements UnsafeSerializable {
/**
* XML type for contact types. This can be either: {@code "admin"}, {@code "billing"}, or

View file

@ -17,6 +17,7 @@ package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.persistence.BillingVKey.BillingEventVKey;
@ -35,7 +36,7 @@ import org.joda.time.DateTime;
@Embed
@MappedSuperclass
@Access(AccessType.FIELD)
public class GracePeriodBase extends ImmutableObject {
public class GracePeriodBase extends ImmutableObject implements UnsafeSerializable {
/** Unique id required for hibernate representation. */
@Transient long gracePeriodId;

View file

@ -16,6 +16,7 @@ package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.xml.bind.annotation.XmlAttribute;
@ -25,7 +26,7 @@ import javax.xml.bind.annotation.XmlValue;
/** The "periodType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
@Embed
@javax.persistence.Embeddable
public class Period extends ImmutableObject {
public class Period extends ImmutableObject implements UnsafeSerializable {
@Enumerated(EnumType.STRING)
@XmlAttribute

View file

@ -27,6 +27,7 @@ import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import java.util.Optional;
import javax.persistence.Embedded;
import javax.xml.bind.annotation.XmlAttribute;
@ -39,7 +40,7 @@ import org.joda.time.DateTime;
@Embed
@XmlType(propOrder = {"noticeId", "expirationTime", "acceptedTime"})
@javax.persistence.Embeddable
public class LaunchNotice extends ImmutableObject {
public class LaunchNotice extends ImmutableObject implements UnsafeSerializable {
/** An empty instance to use in place of null. */
private static final NoticeIdType EMPTY_NOTICE_ID = new NoticeIdType();
@ -47,14 +48,13 @@ public class LaunchNotice extends ImmutableObject {
/** An id with a validator-id attribute. */
@Embed
@javax.persistence.Embeddable
public static class NoticeIdType extends ImmutableObject {
public static class NoticeIdType extends ImmutableObject implements UnsafeSerializable {
/**
* The Trademark Claims Notice ID from
* {@link "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3"}.
* The Trademark Claims Notice ID from <a
* href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3">the RFC</a>.
*/
@XmlValue
String tcnId;
@XmlValue String tcnId;
/** The identifier of the TMDB provider to use, defaulting to the TMCH. */
@IgnoreSave(IfNull.class)

View file

@ -17,6 +17,7 @@ package google.registry.model.domain.secdns;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.MappedSuperclass;
@ -31,7 +32,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@Embed
@MappedSuperclass
@Access(AccessType.FIELD)
public abstract class DomainDsDataBase extends ImmutableObject {
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
@Ignore @XmlTransient @Transient String domainRepoId;

View file

@ -16,6 +16,7 @@ package google.registry.model.domain.secdns;
import static google.registry.model.IdService.allocateId;
import google.registry.model.UnsafeSerializable;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.replay.SqlOnlyEntity;
@ -27,7 +28,8 @@ import javax.persistence.Id;
/** Entity class to represent a historic {@link DelegationSignerData}. */
@Entity
public class DomainDsDataHistory extends DomainDsDataBase implements SqlOnlyEntity {
public class DomainDsDataHistory extends DomainDsDataBase
implements SqlOnlyEntity, UnsafeSerializable {
@Id Long dsDataHistoryRevisionId;

View file

@ -27,6 +27,7 @@ import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -56,7 +57,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlTransient
@Embeddable
@MappedSuperclass
public class Address extends ImmutableObject implements Jsonifiable {
public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
/** The schema validation will enforce that this has 3 lines at most. */
// TODO(b/177569726): Remove this field after migration. We need to figure out how to generate

View file

@ -16,6 +16,7 @@ package google.registry.model.eppcommon;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
@ -35,7 +36,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlTransient
@Embeddable
@MappedSuperclass
public abstract class AuthInfo extends ImmutableObject {
public abstract class AuthInfo extends ImmutableObject implements UnsafeSerializable {
@Embedded protected PasswordAuth pw;
@ -47,7 +48,7 @@ public abstract class AuthInfo extends ImmutableObject {
@Embed
@XmlType(namespace = "urn:ietf:params:xml:ns:eppcom-1.0")
@Embeddable
public static class PasswordAuth extends ImmutableObject {
public static class PasswordAuth extends ImmutableObject implements UnsafeSerializable {
@XmlValue
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
String value;

View file

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import javax.xml.bind.annotation.XmlAttribute;
@ -49,7 +50,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlTransient
@Embeddable
@MappedSuperclass
public class PhoneNumber extends ImmutableObject {
public class PhoneNumber extends ImmutableObject implements UnsafeSerializable {
@XmlValue
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)

View file

@ -18,6 +18,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.xml.bind.annotation.XmlElement;
@ -32,7 +33,7 @@ import javax.xml.bind.annotation.XmlType;
@Embed
@XmlType(propOrder = {"clientTransactionId", "serverTransactionId"})
@javax.persistence.Embeddable
public class Trid extends ImmutableObject {
public class Trid extends ImmutableObject implements UnsafeSerializable {
/** The server transaction id. */
@XmlElement(name = "svTRID", namespace = "urn:ietf:params:xml:ns:epp-1.0")

View file

@ -20,6 +20,7 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.host.HostHistory.HostHistoryId;
import google.registry.model.replay.DatastoreEntity;
import google.registry.model.replay.SqlEntity;
@ -60,7 +61,7 @@ import javax.persistence.PostLoad;
@EntitySubclass
@Access(AccessType.FIELD)
@IdClass(HostHistoryId.class)
public class HostHistory extends HistoryEntry implements SqlEntity {
public class HostHistory extends HistoryEntry implements SqlEntity, UnsafeSerializable {
// Store HostBase instead of HostResource so we don't pick up its @Id
// Nullable for the sake of pre-Registry-3.0 history objects

View file

@ -17,6 +17,7 @@ package google.registry.model.poll;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppResponse.ResponseData;
import javax.persistence.Embeddable;
@ -31,12 +32,13 @@ import org.joda.time.DateTime;
/** The {@link ResponseData} returned when completing a pending action on a domain. */
@XmlTransient
@Embeddable
public class PendingActionNotificationResponse extends ImmutableObject implements ResponseData {
public class PendingActionNotificationResponse extends ImmutableObject
implements ResponseData, UnsafeSerializable {
/** The inner name type that contains a name and the result boolean. */
@Embed
@Embeddable
static class NameOrId extends ImmutableObject {
static class NameOrId extends ImmutableObject implements UnsafeSerializable {
@XmlValue
String value;

View file

@ -33,6 +33,7 @@ import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.contact.ContactResource;
@ -98,7 +99,7 @@ import org.joda.time.DateTime;
@javax.persistence.Index(columnList = "eventTime")
})
public abstract class PollMessage extends ImmutableObject
implements Buildable, DatastoreAndSqlEntity, TransferServerApproveEntity {
implements Buildable, DatastoreAndSqlEntity, TransferServerApproveEntity, UnsafeSerializable {
/** Entity id. */
@Id

View file

@ -72,6 +72,7 @@ import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
@ -116,7 +117,7 @@ import org.joda.time.DateTime;
})
@InCrossTld
public class Registrar extends ImmutableObject
implements Buildable, DatastoreAndSqlEntity, Jsonifiable {
implements Buildable, DatastoreAndSqlEntity, Jsonifiable, UnsafeSerializable {
/** Represents the type of a registrar entity. */
public enum Type {
@ -404,7 +405,7 @@ public class Registrar extends ImmutableObject
/** A billing account entry for this registrar, consisting of a currency and an account Id. */
@Embed
public static class BillingAccountEntry extends ImmutableObject {
public static class BillingAccountEntry extends ImmutableObject implements UnsafeSerializable {
CurrencyUnit currency;
String accountId;

View file

@ -46,6 +46,7 @@ import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.registrar.RegistrarContact.RegistrarPocId;
@ -82,7 +83,7 @@ import javax.persistence.Transient;
@IdClass(RegistrarPocId.class)
@InCrossTld
public class RegistrarContact extends ImmutableObject
implements DatastoreAndSqlEntity, Jsonifiable {
implements DatastoreAndSqlEntity, Jsonifiable, UnsafeSerializable {
@Parent @Transient Key<Registrar> parent;

View file

@ -22,6 +22,7 @@ import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.replay.DatastoreAndSqlEntity;
import javax.persistence.Column;
@ -46,7 +47,7 @@ import org.joda.time.DateTime;
@Embed
@Entity
public class DomainTransactionRecord extends ImmutableObject
implements Buildable, DatastoreAndSqlEntity {
implements Buildable, DatastoreAndSqlEntity, UnsafeSerializable {
@Id
@Ignore

View file

@ -31,6 +31,7 @@ import com.googlecode.objectify.condition.IfNull;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
@ -83,7 +84,8 @@ import org.joda.time.DateTime;
@Entity
@MappedSuperclass
@Access(AccessType.FIELD)
public class HistoryEntry extends ImmutableObject implements Buildable, DatastoreEntity {
public class HistoryEntry extends ImmutableObject
implements Buildable, DatastoreEntity, UnsafeSerializable {
/** Represents the type of history entry. */
public enum Type {

View file

@ -52,6 +52,7 @@ import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
@ -88,7 +89,8 @@ import org.joda.time.Duration;
@Entity
@javax.persistence.Entity(name = "Tld")
@InCrossTld
public class Registry extends ImmutableObject implements Buildable, DatastoreAndSqlEntity {
public class Registry extends ImmutableObject
implements Buildable, DatastoreAndSqlEntity, UnsafeSerializable {
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
@ -308,7 +310,7 @@ public class Registry extends ImmutableObject implements Buildable, DatastoreAnd
* <p>There must be at least one entry in this set.
*
* <p>All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by
* <code>@Inject Map<String, DnsWriter></code>
* {@code @Inject Map<String, DnsWriter>}
*/
@Column(nullable = false)
Set<String> dnsWriters;

View file

@ -16,6 +16,7 @@ package google.registry.model.transfer;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
@ -27,7 +28,7 @@ import org.joda.time.DateTime;
/** Fields common to {@link TransferData} and {@link TransferResponse}. */
@XmlTransient
@MappedSuperclass
public abstract class BaseTransferObject extends ImmutableObject {
public abstract class BaseTransferObject extends ImmutableObject implements UnsafeSerializable {
/**
* The status of the current or last transfer. Can be null if never transferred. Note that we
* leave IgnoreSave off this field so that we can ensure that TransferData loaded from Objectify

View file

@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.persistence.converter.StringMapDescriptor.StringMap;
import java.io.Serializable;
import java.util.Map;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
@ -30,7 +31,8 @@ import org.joda.time.DateTime;
* Base JPA converter for {@link TimedTransitionProperty} objects that are stored in a column with
* data type of hstore in the database.
*/
public abstract class TimedTransitionPropertyConverterBase<K, V extends TimedTransition<K>>
public abstract class TimedTransitionPropertyConverterBase<
K extends Serializable, V extends TimedTransition<K>>
implements AttributeConverter<TimedTransitionProperty<K, V>, StringMap> {
abstract Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<DateTime, V> entry);

View file

@ -25,6 +25,7 @@ import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.SerializeUtils.serializeDeserialize;
import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -46,6 +47,7 @@ import google.registry.persistence.VKey;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.DateTimeUtils;
import org.joda.money.Money;
import org.joda.time.DateTime;
@ -195,6 +197,20 @@ public class BillingEventTest extends EntityTestCase {
ofyTmOrDoNothing(() -> assertThat(tm().loadByEntity(modification)).isEqualTo(modification));
}
@TestSqlOnly
void testSerializable() {
BillingEvent persisted = loadByEntity(oneTime);
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
persisted = loadByEntity(oneTimeSynthetic);
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
persisted = loadByEntity(recurring);
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
persisted = loadByEntity(cancellationOneTime);
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
persisted = loadByEntity(cancellationRecurring);
assertThat(serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyOnly
void testParenting() {
// Note that these are all tested separately because BillingEvent is an abstract base class that

View file

@ -30,6 +30,8 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.tld.Registry;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@ -46,6 +48,15 @@ public class CursorTest extends EntityTestCase {
fakeClock.setTo(DateTime.parse("2010-10-17TZ"));
}
@TestSqlOnly
void testSerializable() {
final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z");
tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, time)));
Cursor persisted =
tm().transact(() -> tm().loadByKey(Cursor.createGlobalVKey(RECURRING_BILLING)));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyAndSql
void testSuccess_persistScopedCursor() {
createTld("tld");

View file

@ -48,6 +48,7 @@ import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import org.junit.jupiter.api.BeforeEach;
/** Unit tests for {@link ContactResource}. */
@ -174,6 +175,14 @@ public class ContactResourceTest extends EntityTestCase {
.hasValue(contactResource);
}
@TestSqlOnly
void testSerializable() {
ContactResource persisted =
loadByForeignKey(ContactResource.class, contactResource.getForeignKey(), fakeClock.nowUtc())
.get();
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyOnly
void testIndexing() throws Exception {
verifyDatastoreIndexing(

View file

@ -56,6 +56,7 @@ import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import java.util.Arrays;
import org.joda.money.Money;
import org.joda.time.DateTime;
@ -354,6 +355,14 @@ public class DomainBaseSqlTest {
});
}
@TestSqlOnly
void testSerializable() {
createTld("com");
insertInDb(contact, contact2, domain, host);
DomainBase persisted = jpaTm().transact(() -> jpaTm().loadByEntity(domain));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestSqlOnly
void testUpdates() {
createTld("com");

View file

@ -41,6 +41,8 @@ import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@ -93,6 +95,44 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(loadByEntity(singleUseToken)).isEqualTo(singleUseToken);
}
@TestSqlOnly
void testSerializable() {
AllocationToken unlimitedUseToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123Unlimited")
.setTokenType(UNLIMITED_USE)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("dev", "app"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar, NewRegistrar"))
.setDiscountFraction(0.5)
.setDiscountPremiums(true)
.setDiscountYears(3)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, NOT_STARTED)
.put(DateTime.now(UTC), TokenStatus.VALID)
.put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED)
.build())
.build());
AllocationToken persisted = loadByEntity(unlimitedUseToken);
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
DomainBase domain = persistActiveDomain("example.foo");
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
AllocationToken singleUseToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123Single")
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.setDomainName("example.foo")
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setTokenType(SINGLE_USE)
.build());
persisted = loadByEntity(singleUseToken);
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyOnly
void testIndexing() throws Exception {
DomainBase domain = persistActiveDomain("blahdomain.foo");

View file

@ -39,6 +39,7 @@ import google.registry.persistence.VKey;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
/** Tests for {@link ContactHistory}. */
@DualDatabaseTest
@ -64,6 +65,18 @@ public class ContactHistoryTest extends EntityTestCase {
});
}
@TestSqlOnly
void testSerializable() {
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
insertInDb(contact);
ContactResource contactFromDb = loadByEntity(contact);
ContactHistory contactHistory = createContactHistory(contactFromDb);
insertInDb(contactHistory);
ContactHistory fromDatabase =
jpaTm().transact(() -> jpaTm().loadByKey(contactHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}
@TestSqlOnly
void testLegacyPersistence_nullContactBase() {
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");

View file

@ -55,6 +55,7 @@ import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@ -87,6 +88,16 @@ public class DomainHistoryTest extends EntityTestCase {
});
}
@TestSqlOnly
void testSerializable() {
DomainBase domain = addGracePeriodForSql(createDomainWithContactsAndHosts());
DomainHistory domainHistory = createDomainHistory(domain);
insertInDb(domainHistory);
DomainHistory fromDatabase =
jpaTm().transact(() -> jpaTm().loadByKey(domainHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}
@TestSqlOnly
void testLegacyPersistence_nullResource() {
DomainBase domain = addGracePeriodForSql(createDomainWithContactsAndHosts());

View file

@ -36,6 +36,7 @@ import google.registry.persistence.VKey;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
/** Tests for {@link HostHistory}. */
@DualDatabaseTest
@ -61,6 +62,17 @@ public class HostHistoryTest extends EntityTestCase {
});
}
@TestSqlOnly
void testSerializable() {
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
insertInDb(host);
HostResource hostFromDb = loadByEntity(host);
HostHistory hostHistory = createHostHistory(hostFromDb);
insertInDb(hostHistory);
HostHistory fromDatabase = jpaTm().transact(() -> jpaTm().loadByKey(hostHistory.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(fromDatabase)).isEqualTo(fromDatabase);
}
@TestSqlOnly
void testLegacyPersistence_nullHostBase() {
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");

View file

@ -45,6 +45,8 @@ import google.registry.model.transfer.TransferStatus;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@ -111,6 +113,14 @@ class HostResourceTest extends EntityTestCase {
.containsExactly(newHost);
}
@TestSqlOnly
void testSerializable() {
HostResource newHost = host.asBuilder().setRepoId("NEWHOST").build();
tm().transact(() -> tm().insert(newHost));
HostResource persisted = tm().transact(() -> tm().loadByEntity(newHost));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyOnly
void testLoadingByForeignKey() {
assertThat(loadByForeignKey(HostResource.class, host.getForeignKey(), fakeClock.nowUtc()))

View file

@ -36,6 +36,7 @@ import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import org.junit.jupiter.api.BeforeEach;
/** Unit tests for {@link PollMessage}. */
@ -118,6 +119,20 @@ public class PollMessageTest extends EntityTestCase {
assertThat(tm().transact(() -> tm().loadByEntity(pollMessage))).isEqualTo(pollMessage);
}
@TestSqlOnly
void testSerializableOneTime() {
PollMessage.OneTime pollMessage =
persistResource(
new PollMessage.OneTime.Builder()
.setRegistrarId("TheRegistrar")
.setEventTime(fakeClock.nowUtc())
.setMsg("Test poll message")
.setParent(historyEntry)
.build());
PollMessage persisted = tm().transact(() -> tm().loadByEntity(pollMessage));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyAndSql
void testPersistenceAutorenew() {
PollMessage.Autorenew pollMessage =
@ -133,6 +148,22 @@ public class PollMessageTest extends EntityTestCase {
assertThat(tm().transact(() -> tm().loadByEntity(pollMessage))).isEqualTo(pollMessage);
}
@TestSqlOnly
void testSerializableAutorenew() {
PollMessage.Autorenew pollMessage =
persistResource(
new PollMessage.Autorenew.Builder()
.setRegistrarId("TheRegistrar")
.setEventTime(fakeClock.nowUtc())
.setMsg("Test poll message")
.setParent(historyEntry)
.setAutorenewEndTime(fakeClock.nowUtc().plusDays(365))
.setTargetId("foobar.foo")
.build());
PollMessage persisted = tm().transact(() -> tm().loadByEntity(pollMessage));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyOnly
void testIndexingAutorenew() throws Exception {
PollMessage.Autorenew pollMessage =

View file

@ -45,7 +45,9 @@ import google.registry.model.tld.Registries;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.CidrAddressBlock;
import google.registry.util.SerializeUtils;
import org.joda.money.CurrencyUnit;
import org.junit.jupiter.api.BeforeEach;
@ -135,6 +137,12 @@ class RegistrarTest extends EntityTestCase {
assertThat(tm().transact(() -> tm().loadByKey(registrar.createVKey()))).isEqualTo(registrar);
}
@TestSqlOnly
void testSerializable() {
Registrar persisted = tm().transact(() -> tm().loadByKey(registrar.createVKey()));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyOnly
void testIndexing() throws Exception {
verifyDatastoreIndexing(registrar, "registrarName", "ianaIdentifier");

View file

@ -47,6 +47,8 @@ import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import google.registry.util.SerializeUtils;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.Money;
@ -87,6 +89,16 @@ public final class RegistryTest extends EntityTestCase {
.isEqualTo(Registry.get("tld"));
}
@TestSqlOnly
void testSerializable() {
ReservedList rl15 = persistReservedList("tld-reserved15", "potato,FULLY_BLOCKED");
Registry registry = Registry.get("tld").asBuilder().setReservedLists(rl15).build();
tm().transact(() -> tm().put(registry));
Registry persisted =
tm().transact(() -> tm().loadByKey(Registry.createVKey(registry.tldStrId)));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
@TestOfyAndSql
void testFailure_registryNotFound() {
createTld("foo");

View file

@ -16,6 +16,7 @@ package google.registry.schema.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarContact.Type.WHOIS;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.SqlHelper.saveRegistrar;
@ -27,6 +28,7 @@ import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.TmOverrideExtension;
import google.registry.util.SerializeUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@ -74,4 +76,11 @@ class RegistrarContactTest {
insertInDb(testRegistrarPoc);
assertThat(loadByEntity(testRegistrarPoc)).isEqualTo(testRegistrarPoc);
}
@Test
void testSerializable_succeeds() {
insertInDb(testRegistrarPoc);
RegistrarContact persisted = jpaTm().transact(() -> jpaTm().loadByEntity(testRegistrarPoc));
assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted);
}
}

View file

@ -65,5 +65,10 @@ public final class SerializeUtils {
}
}
/** Serializes an object then deserializes it. This is typically used in tests. */
public static Object serializeDeserialize(Object object) {
return deserialize(Object.class, serialize(object));
}
private SerializeUtils() {}
}