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() {} }