From 93a479837f3bfc75ea354497535e8f177fdaaefe Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Thu, 28 Oct 2021 12:19:09 -0400 Subject: [PATCH] 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. --- build.gradle | 3 +- .../registry/model/BackupGroupRoot.java | 2 +- .../registry/model/CreateAutoTimestamp.java | 2 +- .../registry/model/ImmutableObject.java | 3 ++ .../registry/model/UnsafeSerializable.java | 34 ++++++++++++++ .../registry/model/UpdateAutoTimestamp.java | 2 +- .../registry/model/billing/BillingEvent.java | 3 +- .../google/registry/model/common/Cursor.java | 6 +-- .../registry/model/common/TimeOfYear.java | 3 +- .../model/common/TimedTransitionProperty.java | 46 +++++++++++-------- .../model/contact/ContactHistory.java | 3 +- .../registry/model/contact/Disclose.java | 6 ++- .../registry/model/contact/PostalInfo.java | 4 +- .../model/domain/DesignatedContact.java | 3 +- .../model/domain/GracePeriodBase.java | 3 +- .../google/registry/model/domain/Period.java | 3 +- .../model/domain/launch/LaunchNotice.java | 12 ++--- .../model/domain/secdns/DomainDsDataBase.java | 3 +- .../domain/secdns/DomainDsDataHistory.java | 4 +- .../registry/model/eppcommon/Address.java | 3 +- .../registry/model/eppcommon/AuthInfo.java | 5 +- .../registry/model/eppcommon/PhoneNumber.java | 3 +- .../google/registry/model/eppcommon/Trid.java | 3 +- .../registry/model/host/HostHistory.java | 3 +- .../PendingActionNotificationResponse.java | 6 ++- .../registry/model/poll/PollMessage.java | 3 +- .../registry/model/registrar/Registrar.java | 5 +- .../model/registrar/RegistrarContact.java | 3 +- .../reporting/DomainTransactionRecord.java | 3 +- .../model/reporting/HistoryEntry.java | 4 +- .../google/registry/model/tld/Registry.java | 6 ++- .../model/transfer/BaseTransferObject.java | 3 +- .../TimedTransitionPropertyConverterBase.java | 4 +- .../model/billing/BillingEventTest.java | 16 +++++++ .../registry/model/common/CursorTest.java | 11 +++++ .../model/contact/ContactResourceTest.java | 9 ++++ .../model/domain/DomainBaseSqlTest.java | 9 ++++ .../domain/token/AllocationTokenTest.java | 40 ++++++++++++++++ .../model/history/ContactHistoryTest.java | 13 ++++++ .../model/history/DomainHistoryTest.java | 11 +++++ .../model/history/HostHistoryTest.java | 12 +++++ .../registry/model/host/HostResourceTest.java | 10 ++++ .../registry/model/poll/PollMessageTest.java | 31 +++++++++++++ .../model/registrar/RegistrarTest.java | 8 ++++ .../registry/model/tld/RegistryTest.java | 12 +++++ .../registrar/RegistrarContactTest.java | 9 ++++ .../google/registry/util/SerializeUtils.java | 5 ++ 47 files changed, 333 insertions(+), 62 deletions(-) create mode 100644 core/src/main/java/google/registry/model/UnsafeSerializable.java diff --git a/build.gradle b/build.gradle index e13f65690..357c07b9d 100644 --- a/build.gradle +++ b/build.gradle @@ -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) diff --git a/core/src/main/java/google/registry/model/BackupGroupRoot.java b/core/src/main/java/google/registry/model/BackupGroupRoot.java index 37682c8ef..78c1a9480 100644 --- a/core/src/main/java/google/registry/model/BackupGroupRoot.java +++ b/core/src/main/java/google/registry/model/BackupGroupRoot.java @@ -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. diff --git a/core/src/main/java/google/registry/model/CreateAutoTimestamp.java b/core/src/main/java/google/registry/model/CreateAutoTimestamp.java index 2a3200514..606e3a13e 100644 --- a/core/src/main/java/google/registry/model/CreateAutoTimestamp.java +++ b/core/src/main/java/google/registry/model/CreateAutoTimestamp.java @@ -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; diff --git a/core/src/main/java/google/registry/model/ImmutableObject.java b/core/src/main/java/google/registry/model/ImmutableObject.java index 27b0e7dda..7144c8a85 100644 --- a/core/src/main/java/google/registry/model/ImmutableObject.java +++ b/core/src/main/java/google/registry/model/ImmutableObject.java @@ -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) { diff --git a/core/src/main/java/google/registry/model/UnsafeSerializable.java b/core/src/main/java/google/registry/model/UnsafeSerializable.java new file mode 100644 index 000000000..e7d9f797d --- /dev/null +++ b/core/src/main/java/google/registry/model/UnsafeSerializable.java @@ -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. + * + *

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}. + * + *

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 {} diff --git a/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java b/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java index 20370af06..5781409ad 100644 --- a/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java +++ b/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java @@ -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 diff --git a/core/src/main/java/google/registry/model/billing/BillingEvent.java b/core/src/main/java/google/registry/model/billing/BillingEvent.java index c4910018c..f702fd9ab 100644 --- a/core/src/main/java/google/registry/model/billing/BillingEvent.java +++ b/core/src/main/java/google/registry/model/billing/BillingEvent.java @@ -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 { diff --git a/core/src/main/java/google/registry/model/common/Cursor.java b/core/src/main/java/google/registry/model/common/Cursor.java index 409906877..2aca2ad80 100644 --- a/core/src/main/java/google/registry/model/common/Cursor.java +++ b/core/src/main/java/google/registry/model/common/Cursor.java @@ -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; diff --git a/core/src/main/java/google/registry/model/common/TimeOfYear.java b/core/src/main/java/google/registry/model/common/TimeOfYear.java index e08ad0d6c..f6663cdef 100644 --- a/core/src/main/java/google/registry/model/common/TimeOfYear.java +++ b/core/src/main/java/google/registry/model/common/TimeOfYear.java @@ -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 diff --git a/core/src/main/java/google/registry/model/common/TimedTransitionProperty.java b/core/src/main/java/google/registry/model/common/TimedTransitionProperty.java index 2d0ad62bb..65fab8076 100644 --- a/core/src/main/java/google/registry/model/common/TimedTransitionProperty.java +++ b/core/src/main/java/google/registry/model/common/TimedTransitionProperty.java @@ -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> - extends ForwardingMap { +public class TimedTransitionProperty< + V extends Serializable, T extends TimedTransitionProperty.TimedTransition> + extends ForwardingMap 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 TimedTransitionPropertyThe 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 extends ImmutableObject { + public abstract static class TimedTransition extends ImmutableObject + implements UnsafeSerializable { /** The time at which this value becomes the active value. */ private DateTime transitionTime; @@ -89,16 +93,16 @@ public class TimedTransitionProperty parameter could be eliminated by getting the class via reflection, but then // the callsite cannot infer T, so unless you explicitly call this as .fromValueMap() it // will default to using just TimedTransition, which fails at runtime. - private static > NavigableMap makeTransitionMap( - ImmutableSortedMap valueMap, - final Class timedTransitionSubclass) { + private static > + NavigableMap makeTransitionMap( + ImmutableSortedMap valueMap, final Class 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 TimedTransitionPropertyThis method should be the normal method for constructing a {@link TimedTransitionProperty}. */ - public static > TimedTransitionProperty fromValueMap( - ImmutableSortedMap valueMap, - final Class timedTransitionSubclass) { + public static > + TimedTransitionProperty fromValueMap( + ImmutableSortedMap valueMap, final Class timedTransitionSubclass) { return new TimedTransitionProperty<>(ImmutableSortedMap.copyOf( makeTransitionMap(valueMap, timedTransitionSubclass))); } @@ -175,10 +179,10 @@ public class TimedTransitionProperty> + public static > TimedTransitionProperty make( ImmutableSortedMap newTransitions, Class transitionClass, @@ -200,7 +204,7 @@ public class TimedTransitionProperty> + public static > void validateTimedTransitionMap( @Nullable NavigableMap transitionMap, ImmutableMultimap allowedTransitions, @@ -240,8 +244,9 @@ public class TimedTransitionProperty> TimedTransitionProperty forMapify( - ImmutableSortedMap valueMap, Class timedTransitionSubclass) { + public static > + TimedTransitionProperty forMapify( + ImmutableSortedMap valueMap, Class timedTransitionSubclass) { return new TimedTransitionProperty<>( new TreeMap<>(makeTransitionMap(valueMap, timedTransitionSubclass))); } @@ -254,8 +259,9 @@ public class TimedTransitionProperty> TimedTransitionProperty forMapify( - V valueAtStartOfTime, Class timedTransitionSubclass) { + public static > + TimedTransitionProperty forMapify( + V valueAtStartOfTime, Class timedTransitionSubclass) { return forMapify( ImmutableSortedMap.of(START_OF_TIME, valueAtStartOfTime), timedTransitionSubclass); } diff --git a/core/src/main/java/google/registry/model/contact/ContactHistory.java b/core/src/main/java/google/registry/model/contact/ContactHistory.java index bc0e4794a..ab2f6de18 100644 --- a/core/src/main/java/google/registry/model/contact/ContactHistory.java +++ b/core/src/main/java/google/registry/model/contact/ContactHistory.java @@ -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 diff --git a/core/src/main/java/google/registry/model/contact/Disclose.java b/core/src/main/java/google/registry/model/contact/Disclose.java index 25923376e..2e96eb453 100644 --- a/core/src/main/java/google/registry/model/contact/Disclose.java +++ b/core/src/main/java/google/registry/model/contact/Disclose.java @@ -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 name; @@ -78,7 +80,7 @@ public class Disclose extends ImmutableObject { /** The "intLocType" from RFC5733. */ @Embed - public static class PostalInfoChoice extends ImmutableObject { + public static class PostalInfoChoice extends ImmutableObject implements Serializable { @XmlAttribute PostalInfo.Type type; diff --git a/core/src/main/java/google/registry/model/contact/PostalInfo.java b/core/src/main/java/google/registry/model/contact/PostalInfo.java index dcc2b90a3..0fb422f21 100644 --- a/core/src/main/java/google/registry/model/contact/PostalInfo.java +++ b/core/src/main/java/google/registry/model/contact/PostalInfo.java @@ -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 { +public class PostalInfo extends ImmutableObject + implements Overlayable, UnsafeSerializable { /** The type of the address, either localized or international. */ public enum Type { diff --git a/core/src/main/java/google/registry/model/domain/DesignatedContact.java b/core/src/main/java/google/registry/model/domain/DesignatedContact.java index e38f2a3c6..e4d1e39a5 100644 --- a/core/src/main/java/google/registry/model/domain/DesignatedContact.java +++ b/core/src/main/java/google/registry/model/domain/DesignatedContact.java @@ -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 diff --git a/core/src/main/java/google/registry/model/domain/GracePeriodBase.java b/core/src/main/java/google/registry/model/domain/GracePeriodBase.java index b6894648c..0c22ed065 100644 --- a/core/src/main/java/google/registry/model/domain/GracePeriodBase.java +++ b/core/src/main/java/google/registry/model/domain/GracePeriodBase.java @@ -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; diff --git a/core/src/main/java/google/registry/model/domain/Period.java b/core/src/main/java/google/registry/model/domain/Period.java index 6d0df8df3..c2841bf7a 100644 --- a/core/src/main/java/google/registry/model/domain/Period.java +++ b/core/src/main/java/google/registry/model/domain/Period.java @@ -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 RFC5731. */ @Embed @javax.persistence.Embeddable -public class Period extends ImmutableObject { +public class Period extends ImmutableObject implements UnsafeSerializable { @Enumerated(EnumType.STRING) @XmlAttribute diff --git a/core/src/main/java/google/registry/model/domain/launch/LaunchNotice.java b/core/src/main/java/google/registry/model/domain/launch/LaunchNotice.java index df1d2ba3f..048a333d4 100644 --- a/core/src/main/java/google/registry/model/domain/launch/LaunchNotice.java +++ b/core/src/main/java/google/registry/model/domain/launch/LaunchNotice.java @@ -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 the RFC. */ - @XmlValue - String tcnId; + @XmlValue String tcnId; /** The identifier of the TMDB provider to use, defaulting to the TMCH. */ @IgnoreSave(IfNull.class) diff --git a/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java b/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java index f09516aa1..739c7e20e 100644 --- a/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java +++ b/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java @@ -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; diff --git a/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataHistory.java b/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataHistory.java index f7ea90cb4..4d1e646a3 100644 --- a/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataHistory.java +++ b/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataHistory.java @@ -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; diff --git a/core/src/main/java/google/registry/model/eppcommon/Address.java b/core/src/main/java/google/registry/model/eppcommon/Address.java index 28cf6f558..1a4de6b41 100644 --- a/core/src/main/java/google/registry/model/eppcommon/Address.java +++ b/core/src/main/java/google/registry/model/eppcommon/Address.java @@ -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 diff --git a/core/src/main/java/google/registry/model/eppcommon/AuthInfo.java b/core/src/main/java/google/registry/model/eppcommon/AuthInfo.java index 4fdd4f2a7..3f89c45f8 100644 --- a/core/src/main/java/google/registry/model/eppcommon/AuthInfo.java +++ b/core/src/main/java/google/registry/model/eppcommon/AuthInfo.java @@ -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; diff --git a/core/src/main/java/google/registry/model/eppcommon/PhoneNumber.java b/core/src/main/java/google/registry/model/eppcommon/PhoneNumber.java index 22a854e81..dfb1919e5 100644 --- a/core/src/main/java/google/registry/model/eppcommon/PhoneNumber.java +++ b/core/src/main/java/google/registry/model/eppcommon/PhoneNumber.java @@ -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) diff --git a/core/src/main/java/google/registry/model/eppcommon/Trid.java b/core/src/main/java/google/registry/model/eppcommon/Trid.java index 471f95440..f16b6f90c 100644 --- a/core/src/main/java/google/registry/model/eppcommon/Trid.java +++ b/core/src/main/java/google/registry/model/eppcommon/Trid.java @@ -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") diff --git a/core/src/main/java/google/registry/model/host/HostHistory.java b/core/src/main/java/google/registry/model/host/HostHistory.java index 2ef1b1bb8..0041da076 100644 --- a/core/src/main/java/google/registry/model/host/HostHistory.java +++ b/core/src/main/java/google/registry/model/host/HostHistory.java @@ -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 diff --git a/core/src/main/java/google/registry/model/poll/PendingActionNotificationResponse.java b/core/src/main/java/google/registry/model/poll/PendingActionNotificationResponse.java index 1e96de44d..32c94726a 100644 --- a/core/src/main/java/google/registry/model/poll/PendingActionNotificationResponse.java +++ b/core/src/main/java/google/registry/model/poll/PendingActionNotificationResponse.java @@ -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; diff --git a/core/src/main/java/google/registry/model/poll/PollMessage.java b/core/src/main/java/google/registry/model/poll/PollMessage.java index 6903dc21c..d8232f756 100644 --- a/core/src/main/java/google/registry/model/poll/PollMessage.java +++ b/core/src/main/java/google/registry/model/poll/PollMessage.java @@ -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 diff --git a/core/src/main/java/google/registry/model/registrar/Registrar.java b/core/src/main/java/google/registry/model/registrar/Registrar.java index c66d479b6..75cf0dd33 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -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; diff --git a/core/src/main/java/google/registry/model/registrar/RegistrarContact.java b/core/src/main/java/google/registry/model/registrar/RegistrarContact.java index aff28987b..d4aefc041 100644 --- a/core/src/main/java/google/registry/model/registrar/RegistrarContact.java +++ b/core/src/main/java/google/registry/model/registrar/RegistrarContact.java @@ -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 parent; diff --git a/core/src/main/java/google/registry/model/reporting/DomainTransactionRecord.java b/core/src/main/java/google/registry/model/reporting/DomainTransactionRecord.java index 01e0f81e3..1f9ccaf8d 100644 --- a/core/src/main/java/google/registry/model/reporting/DomainTransactionRecord.java +++ b/core/src/main/java/google/registry/model/reporting/DomainTransactionRecord.java @@ -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 diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java index 42b38f0ef..a09c69588 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java @@ -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 { diff --git a/core/src/main/java/google/registry/model/tld/Registry.java b/core/src/main/java/google/registry/model/tld/Registry.java index 71d731a73..faa81b26a 100644 --- a/core/src/main/java/google/registry/model/tld/Registry.java +++ b/core/src/main/java/google/registry/model/tld/Registry.java @@ -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 parent = getCrossTldKey(); @@ -308,7 +310,7 @@ public class Registry extends ImmutableObject implements Buildable, DatastoreAnd *

There must be at least one entry in this set. * *

All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by - * @Inject Map + * {@code @Inject Map} */ @Column(nullable = false) Set dnsWriters; diff --git a/core/src/main/java/google/registry/model/transfer/BaseTransferObject.java b/core/src/main/java/google/registry/model/transfer/BaseTransferObject.java index f87fec788..bf92f1be4 100644 --- a/core/src/main/java/google/registry/model/transfer/BaseTransferObject.java +++ b/core/src/main/java/google/registry/model/transfer/BaseTransferObject.java @@ -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 diff --git a/core/src/main/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBase.java b/core/src/main/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBase.java index 57869df40..ff69ea3d4 100644 --- a/core/src/main/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBase.java +++ b/core/src/main/java/google/registry/persistence/converter/TimedTransitionPropertyConverterBase.java @@ -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> +public abstract class TimedTransitionPropertyConverterBase< + K extends Serializable, V extends TimedTransition> implements AttributeConverter, StringMap> { abstract Map.Entry convertToDatabaseMapEntry(Map.Entry entry); diff --git a/core/src/test/java/google/registry/model/billing/BillingEventTest.java b/core/src/test/java/google/registry/model/billing/BillingEventTest.java index 6337207d2..d7b284e35 100644 --- a/core/src/test/java/google/registry/model/billing/BillingEventTest.java +++ b/core/src/test/java/google/registry/model/billing/BillingEventTest.java @@ -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 diff --git a/core/src/test/java/google/registry/model/common/CursorTest.java b/core/src/test/java/google/registry/model/common/CursorTest.java index 40f38472f..a61cdd663 100644 --- a/core/src/test/java/google/registry/model/common/CursorTest.java +++ b/core/src/test/java/google/registry/model/common/CursorTest.java @@ -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"); diff --git a/core/src/test/java/google/registry/model/contact/ContactResourceTest.java b/core/src/test/java/google/registry/model/contact/ContactResourceTest.java index ad6e59c18..1de2cc034 100644 --- a/core/src/test/java/google/registry/model/contact/ContactResourceTest.java +++ b/core/src/test/java/google/registry/model/contact/ContactResourceTest.java @@ -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( diff --git a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java index f5cb14e7b..578ceb508 100644 --- a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java @@ -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"); diff --git a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java index 120cd8fdf..746b9d218 100644 --- a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java +++ b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java @@ -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.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 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"); diff --git a/core/src/test/java/google/registry/model/history/ContactHistoryTest.java b/core/src/test/java/google/registry/model/history/ContactHistoryTest.java index 15f46c0b6..5d8d61975 100644 --- a/core/src/test/java/google/registry/model/history/ContactHistoryTest.java +++ b/core/src/test/java/google/registry/model/history/ContactHistoryTest.java @@ -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"); diff --git a/core/src/test/java/google/registry/model/history/DomainHistoryTest.java b/core/src/test/java/google/registry/model/history/DomainHistoryTest.java index 7bc2ce962..7b5a04a48 100644 --- a/core/src/test/java/google/registry/model/history/DomainHistoryTest.java +++ b/core/src/test/java/google/registry/model/history/DomainHistoryTest.java @@ -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()); diff --git a/core/src/test/java/google/registry/model/history/HostHistoryTest.java b/core/src/test/java/google/registry/model/history/HostHistoryTest.java index a45431f1f..13f9afb95 100644 --- a/core/src/test/java/google/registry/model/history/HostHistoryTest.java +++ b/core/src/test/java/google/registry/model/history/HostHistoryTest.java @@ -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"); diff --git a/core/src/test/java/google/registry/model/host/HostResourceTest.java b/core/src/test/java/google/registry/model/host/HostResourceTest.java index 7d82f9ad6..ec3032411 100644 --- a/core/src/test/java/google/registry/model/host/HostResourceTest.java +++ b/core/src/test/java/google/registry/model/host/HostResourceTest.java @@ -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())) diff --git a/core/src/test/java/google/registry/model/poll/PollMessageTest.java b/core/src/test/java/google/registry/model/poll/PollMessageTest.java index 76beca234..db44ea26e 100644 --- a/core/src/test/java/google/registry/model/poll/PollMessageTest.java +++ b/core/src/test/java/google/registry/model/poll/PollMessageTest.java @@ -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 = diff --git a/core/src/test/java/google/registry/model/registrar/RegistrarTest.java b/core/src/test/java/google/registry/model/registrar/RegistrarTest.java index 2f34418da..09cf69081 100644 --- a/core/src/test/java/google/registry/model/registrar/RegistrarTest.java +++ b/core/src/test/java/google/registry/model/registrar/RegistrarTest.java @@ -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"); diff --git a/core/src/test/java/google/registry/model/tld/RegistryTest.java b/core/src/test/java/google/registry/model/tld/RegistryTest.java index a4fef56a3..4f687fe18 100644 --- a/core/src/test/java/google/registry/model/tld/RegistryTest.java +++ b/core/src/test/java/google/registry/model/tld/RegistryTest.java @@ -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"); diff --git a/core/src/test/java/google/registry/schema/registrar/RegistrarContactTest.java b/core/src/test/java/google/registry/schema/registrar/RegistrarContactTest.java index 6bbe91eb2..b90996d4f 100644 --- a/core/src/test/java/google/registry/schema/registrar/RegistrarContactTest.java +++ b/core/src/test/java/google/registry/schema/registrar/RegistrarContactTest.java @@ -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); + } } diff --git a/util/src/main/java/google/registry/util/SerializeUtils.java b/util/src/main/java/google/registry/util/SerializeUtils.java index efa44e107..2d53d65e8 100644 --- a/util/src/main/java/google/registry/util/SerializeUtils.java +++ b/util/src/main/java/google/registry/util/SerializeUtils.java @@ -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() {} }