diff --git a/core/src/main/java/google/registry/model/domain/DomainBase.java b/core/src/main/java/google/registry/model/domain/DomainBase.java index 652d75a96..28c6ab3b1 100644 --- a/core/src/main/java/google/registry/model/domain/DomainBase.java +++ b/core/src/main/java/google/registry/model/domain/DomainBase.java @@ -66,6 +66,7 @@ import google.registry.model.registry.Registry; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; import google.registry.persistence.VKey; +import google.registry.schema.replay.DatastoreAndSqlEntity; import google.registry.util.CollectionUtils; import java.util.HashSet; import java.util.Objects; @@ -107,7 +108,7 @@ import org.joda.time.Interval; }) @ExternalMessagingName("domain") public class DomainBase extends EppResource - implements ForeignKeyedEppResource, ResourceWithTransferData { + implements ForeignKeyedEppResource, ResourceWithTransferData, DatastoreAndSqlEntity { /** The max number of years that a domain can be registered for, as set by ICANN policy. */ public static final int MAX_REGISTRATION_YEARS = 10; 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 6d6d191ee..4758e1dda 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -73,6 +73,7 @@ import google.registry.model.annotations.ReportedOn; import google.registry.model.common.EntityGroupRoot; import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper; import google.registry.model.registry.Registry; +import google.registry.schema.replay.DatastoreAndSqlEntity; import google.registry.util.CidrAddressBlock; import java.security.cert.CertificateParsingException; import java.util.Comparator; @@ -107,11 +108,12 @@ import org.joda.time.DateTime; columnList = "ianaIdentifier", name = "registrar_iana_identifier_idx"), }) -public class Registrar extends ImmutableObject implements Buildable, Jsonifiable { +public class Registrar extends ImmutableObject + implements Buildable, Jsonifiable, DatastoreAndSqlEntity { /** Represents the type of a registrar entity. */ public enum Type { - /** A real-world, third-party registrar. Should have non-null IANA and billing IDs. */ + /** A real-world, third-party registrar. Should have non-null IANA and billing IDs. */ REAL(Objects::nonNull), /** diff --git a/core/src/main/java/google/registry/schema/replay/DatastoreAndSqlEntity.java b/core/src/main/java/google/registry/schema/replay/DatastoreAndSqlEntity.java new file mode 100644 index 000000000..8b61fde75 --- /dev/null +++ b/core/src/main/java/google/registry/schema/replay/DatastoreAndSqlEntity.java @@ -0,0 +1,31 @@ +// Copyright 2020 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.schema.replay; + +import com.google.common.collect.ImmutableList; + +/** An entity that has the same Java object representation in SQL and Datastore. */ +public interface DatastoreAndSqlEntity extends DatastoreEntity, SqlEntity { + + @Override + default ImmutableList toDatastoreEntities() { + return ImmutableList.of(this); + } + + @Override + default ImmutableList toSqlEntities() { + return ImmutableList.of(this); + } +} diff --git a/core/src/main/java/google/registry/schema/replay/DatastoreEntity.java b/core/src/main/java/google/registry/schema/replay/DatastoreEntity.java new file mode 100644 index 000000000..ecc3f6808 --- /dev/null +++ b/core/src/main/java/google/registry/schema/replay/DatastoreEntity.java @@ -0,0 +1,30 @@ +// Copyright 2020 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.schema.replay; + +import com.google.common.collect.ImmutableList; + +/** + * An object that can be stored in Datastore and serialized using Objectify's {@link + * com.googlecode.objectify.cmd.Saver#toEntity(Object)} code. + * + *

This is used when replaying {@link google.registry.model.ofy.CommitLogManifest}s to import + * transactions and data into the secondary SQL store during the first, Datastore-primary, phase of + * the migration. + */ +public interface DatastoreEntity { + + ImmutableList toSqlEntities(); +} diff --git a/core/src/main/java/google/registry/schema/replay/SqlEntity.java b/core/src/main/java/google/registry/schema/replay/SqlEntity.java new file mode 100644 index 000000000..1a8ea63bc --- /dev/null +++ b/core/src/main/java/google/registry/schema/replay/SqlEntity.java @@ -0,0 +1,29 @@ +// Copyright 2020 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.schema.replay; + +import com.google.common.collect.ImmutableList; + +/** + * An object that can be stored in Cloud SQL using {@link + * javax.persistence.EntityManager#persist(Object)} + * + *

This will be used when replaying SQL transactions into Datastore, during the second, + * SQL-primary, phase of the migration from Datastore to SQL. + */ +public interface SqlEntity { + + ImmutableList toDatastoreEntities(); +} diff --git a/core/src/test/java/google/registry/schema/replay/EntityTest.java b/core/src/test/java/google/registry/schema/replay/EntityTest.java new file mode 100644 index 000000000..7f430b5bf --- /dev/null +++ b/core/src/test/java/google/registry/schema/replay/EntityTest.java @@ -0,0 +1,65 @@ +// Copyright 2020 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.schema.replay; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableSet; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Test to verify classes implement {@link SqlEntity} and {@link DatastoreEntity} when they should. + */ +public class EntityTest { + + @Test + @Ignore("This won't be done until b/152410794 is done, since it requires many entity changes") + public void testSqlEntityPersistence() { + try (ScanResult scanResult = + new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry").scan()) { + // All javax.persistence entities must implement SqlEntity and vice versa + ImmutableSet javaxPersistenceClasses = + getClassNames( + scanResult.getClassesWithAnnotation(javax.persistence.Entity.class.getName())); + ImmutableSet sqlEntityClasses = + getClassNames(scanResult.getClassesImplementing(SqlEntity.class.getName())); + assertThat(javaxPersistenceClasses).isEqualTo(sqlEntityClasses); + + // All com.googlecode.objectify.annotation.Entity classes must implement DatastoreEntity and + // vice versa + ImmutableSet objectifyClasses = + getClassNames( + scanResult.getClassesWithAnnotation( + com.googlecode.objectify.annotation.Entity.class.getName())); + ImmutableSet datastoreEntityClasses = + getClassNames(scanResult.getClassesImplementing(DatastoreEntity.class.getName())); + assertThat(objectifyClasses).isEqualTo(datastoreEntityClasses); + } + } + + private ImmutableSet getClassNames(ClassInfoList classInfoList) { + return classInfoList.stream() + .filter(ClassInfo::isStandardClass) + .map(ClassInfo::loadClass) + .map(Class::getName) + .collect(toImmutableSet()); + } +}