diff --git a/core/src/main/java/google/registry/model/rde/RdeRevision.java b/core/src/main/java/google/registry/model/rde/RdeRevision.java
index 2b50840d7..9fbad8f8b 100644
--- a/core/src/main/java/google/registry/model/rde/RdeRevision.java
+++ b/core/src/main/java/google/registry/model/rde/RdeRevision.java
@@ -15,17 +15,32 @@
package google.registry.model.rde;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
-import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.rde.RdeNamingUtils.makePartialName;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
+import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
+import com.googlecode.objectify.annotation.Ignore;
+import google.registry.model.BackupGroupRoot;
import google.registry.model.ImmutableObject;
+import google.registry.model.rde.RdeRevision.RdeRevisionId;
+import google.registry.persistence.VKey;
+import google.registry.persistence.converter.LocalDateConverter;
+import google.registry.schema.replay.DatastoreEntity;
+import google.registry.schema.replay.SqlEntity;
+import java.io.Serializable;
+import java.util.Optional;
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.IdClass;
+import javax.persistence.Transient;
import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
/**
* Datastore entity for tracking RDE revisions.
@@ -35,32 +50,67 @@ import org.joda.time.DateTime;
* flag is included in the generated XML.
*/
@Entity
-public final class RdeRevision extends ImmutableObject {
+@javax.persistence.Entity
+@IdClass(RdeRevisionId.class)
+public final class RdeRevision extends BackupGroupRoot implements DatastoreEntity, SqlEntity {
/** String triplet of tld, date, and mode, e.g. {@code soy_2015-09-01_full}. */
- @Id
- String id;
+ @Id @Transient String id;
+
+ @javax.persistence.Id @Ignore String tld;
+
+ @javax.persistence.Id @Ignore LocalDate date;
+
+ @javax.persistence.Id @Ignore RdeMode mode;
/**
* Number of last revision successfully staged to GCS.
*
*
This values begins at zero upon object creation and thenceforth incremented transactionally.
*/
+ @Column(nullable = false)
int revision;
+ /** Hibernate requires an empty constructor. */
+ private RdeRevision() {}
+
+ public static RdeRevision create(
+ String id, String tld, LocalDate date, RdeMode mode, int revision) {
+ RdeRevision instance = new RdeRevision();
+ instance.id = id;
+ instance.tld = tld;
+ instance.date = date;
+ instance.mode = mode;
+ instance.revision = revision;
+ return instance;
+ }
+
public int getRevision() {
return revision;
}
+ @Override
+ public ImmutableList toSqlEntities() {
+ return ImmutableList.of(); // we don't care about RdeRevision history
+ }
+
+ @Override
+ public ImmutableList toDatastoreEntities() {
+ return ImmutableList.of(); // we don't care about RdeRevision history
+ }
+
/**
* Returns next revision ID to use when staging a new deposit file for the given triplet.
*
* @return {@code 0} for first deposit generation and {@code >0} for resends
*/
public static int getNextRevision(String tld, DateTime date, RdeMode mode) {
- RdeRevision object =
- ofy().load().type(RdeRevision.class).id(makePartialName(tld, date, mode)).now();
- return object == null ? 0 : object.revision + 1;
+ String id = makePartialName(tld, date, mode);
+ RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
+ Key ofyKey = Key.create(RdeRevision.class, id);
+ Optional revisionOptional =
+ tm().maybeLoad(VKey.create(RdeRevision.class, sqlKey, ofyKey));
+ return revisionOptional.map(rdeRevision -> rdeRevision.revision + 1).orElse(0);
}
/**
@@ -76,17 +126,56 @@ public final class RdeRevision extends ImmutableObject {
checkArgument(revision >= 0, "Negative revision: %s", revision);
String triplet = makePartialName(tld, date, mode);
tm().assertInTransaction();
- RdeRevision object = ofy().load().type(RdeRevision.class).id(triplet).now();
+ RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
+ Key ofyKey = Key.create(RdeRevision.class, triplet);
+ Optional revisionOptional =
+ tm().maybeLoad(VKey.create(RdeRevision.class, sqlKey, ofyKey));
if (revision == 0) {
- verify(object == null, "RdeRevision object already created: %s", object);
+ revisionOptional.ifPresent(
+ rdeRevision -> {
+ throw new IllegalArgumentException(
+ String.format(
+ "RdeRevision object already created and revision 0 specified: %s",
+ rdeRevision));
+ });
} else {
- verifyNotNull(object, "RDE revision object missing for %s?! revision=%s", triplet, revision);
- verify(object.revision == revision - 1,
- "RDE revision object should be at %s but was: %s", revision - 1, object);
+ checkArgument(
+ revisionOptional.isPresent(),
+ "Couldn't find existing RDE revision %s when trying to save new revision %s",
+ triplet,
+ revision);
+ checkArgument(
+ revisionOptional.get().revision == revision - 1,
+ "RDE revision object should be at revision %s but was: %s",
+ revision - 1,
+ revisionOptional.get());
+ }
+ RdeRevision object = RdeRevision.create(triplet, tld, date.toLocalDate(), mode, revision);
+ tm().put(object);
+ }
+
+ /** Class to represent the composite primary key of {@link RdeRevision} entity. */
+ static class RdeRevisionId extends ImmutableObject implements Serializable {
+
+ String tld;
+
+ // Auto-conversion doesn't work for ID classes, we must specify @Column and @Convert
+ @Column(columnDefinition = "date")
+ @Convert(converter = LocalDateConverter.class)
+ LocalDate date;
+
+ @Enumerated(EnumType.STRING)
+ RdeMode mode;
+
+ /** Hibernate requires this default constructor. */
+ private RdeRevisionId() {}
+
+ static RdeRevisionId create(String tld, LocalDate date, RdeMode mode) {
+ RdeRevisionId instance = new RdeRevisionId();
+ instance.tld = tld;
+ instance.date = date;
+ instance.mode = mode;
+ return instance;
}
- object = new RdeRevision();
- object.id = triplet;
- object.revision = revision;
- ofy().save().entity(object);
}
}
diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml
index 1d41fc06e..33f44f824 100644
--- a/core/src/main/resources/META-INF/persistence.xml
+++ b/core/src/main/resources/META-INF/persistence.xml
@@ -53,6 +53,7 @@
google.registry.model.poll.PollMessage
google.registry.model.poll.PollMessage$OneTime
google.registry.model.poll.PollMessage$Autorenew
+ google.registry.model.rde.RdeRevision
google.registry.model.registrar.Registrar
google.registry.model.registrar.RegistrarContact
google.registry.model.registry.label.PremiumList
diff --git a/core/src/test/java/google/registry/model/rde/RdeRevisionTest.java b/core/src/test/java/google/registry/model/rde/RdeRevisionTest.java
index 3ae9a1193..f9e27548b 100644
--- a/core/src/test/java/google/registry/model/rde/RdeRevisionTest.java
+++ b/core/src/test/java/google/registry/model/rde/RdeRevisionTest.java
@@ -15,118 +15,116 @@
package google.registry.model.rde;
import static com.google.common.truth.Truth.assertThat;
-import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.rde.RdeMode.FULL;
import static google.registry.model.rde.RdeRevision.getNextRevision;
import static google.registry.model.rde.RdeRevision.saveRevision;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import com.google.common.base.VerifyException;
-import google.registry.testing.AppEngineExtension;
+import google.registry.model.EntityTestCase;
+import google.registry.testing.DualDatabaseTest;
import org.joda.time.DateTime;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
/** Unit tests for {@link RdeRevision}. */
-public class RdeRevisionTest {
+@DualDatabaseTest
+public class RdeRevisionTest extends EntityTestCase {
- @RegisterExtension
- final AppEngineExtension appEngine =
- AppEngineExtension.builder().withDatastoreAndCloudSql().build();
+ public RdeRevisionTest() {
+ super(JpaEntityCoverageCheck.ENABLED);
+ }
- @Test
+ @BeforeEach
+ void beforeEach() {
+ fakeClock.setTo(DateTime.parse("1984-12-18TZ"));
+ }
+
+ @TestTemplate
void testGetNextRevision_objectDoesntExist_returnsZero() {
- assertThat(getNextRevision("torment", DateTime.parse("1984-12-18TZ"), FULL)).isEqualTo(0);
+ tm().transact(
+ () -> assertThat(getNextRevision("torment", fakeClock.nowUtc(), FULL)).isEqualTo(0));
}
- @Test
+ @TestTemplate
void testGetNextRevision_objectExistsAtZero_returnsOne() {
- save("sorrow", DateTime.parse("1984-12-18TZ"), FULL, 0);
- assertThat(getNextRevision("sorrow", DateTime.parse("1984-12-18TZ"), FULL)).isEqualTo(1);
+ save("sorrow", fakeClock.nowUtc(), FULL, 0);
+ tm().transact(
+ () -> assertThat(getNextRevision("sorrow", fakeClock.nowUtc(), FULL)).isEqualTo(1));
}
- @Test
+ @TestTemplate
void testSaveRevision_objectDoesntExist_newRevisionIsZero_nextRevIsOne() {
- tm().transact(() -> saveRevision("despondency", DateTime.parse("1984-12-18TZ"), FULL, 0));
+ tm().transact(() -> saveRevision("despondency", fakeClock.nowUtc(), FULL, 0));
tm().transact(
() ->
- assertThat(getNextRevision("despondency", DateTime.parse("1984-12-18TZ"), FULL))
- .isEqualTo(1));
+ assertThat(getNextRevision("despondency", fakeClock.nowUtc(), FULL)).isEqualTo(1));
}
- @Test
+ @TestTemplate
void testSaveRevision_objectDoesntExist_newRevisionIsOne_throwsVe() {
- VerifyException thrown =
+ IllegalArgumentException thrown =
assertThrows(
- VerifyException.class,
- () ->
- tm().transact(
- () ->
- saveRevision("despondency", DateTime.parse("1984-12-18TZ"), FULL, 1)));
- assertThat(thrown).hasMessageThat().contains("object missing");
+ IllegalArgumentException.class,
+ () -> tm().transact(() -> saveRevision("despondency", fakeClock.nowUtc(), FULL, 1)));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Couldn't find existing RDE revision despondency_1984-12-18_full "
+ + "when trying to save new revision 1");
}
- @Test
+ @TestTemplate
void testSaveRevision_objectExistsAtZero_newRevisionIsZero_throwsVe() {
- save("melancholy", DateTime.parse("1984-12-18TZ"), FULL, 0);
- VerifyException thrown =
+ save("melancholy", fakeClock.nowUtc(), FULL, 0);
+ IllegalArgumentException thrown =
assertThrows(
- VerifyException.class,
- () ->
- tm().transact(
- () -> saveRevision("melancholy", DateTime.parse("1984-12-18TZ"), FULL, 0)));
+ IllegalArgumentException.class,
+ () -> tm().transact(() -> saveRevision("melancholy", fakeClock.nowUtc(), FULL, 0)));
assertThat(thrown).hasMessageThat().contains("object already created");
}
- @Test
+ @TestTemplate
void testSaveRevision_objectExistsAtZero_newRevisionIsOne_nextRevIsTwo() {
- save("melancholy", DateTime.parse("1984-12-18TZ"), FULL, 0);
- tm().transact(() -> saveRevision("melancholy", DateTime.parse("1984-12-18TZ"), FULL, 1));
- tm().transact(
- () ->
- assertThat(getNextRevision("melancholy", DateTime.parse("1984-12-18TZ"), FULL))
- .isEqualTo(2));
+ DateTime startOfDay = fakeClock.nowUtc().withTimeAtStartOfDay();
+ save("melancholy", startOfDay, FULL, 0);
+ fakeClock.advanceOneMilli();
+ tm().transact(() -> saveRevision("melancholy", startOfDay, FULL, 1));
+ tm().transact(() -> assertThat(getNextRevision("melancholy", startOfDay, FULL)).isEqualTo(2));
}
- @Test
+ @TestTemplate
void testSaveRevision_objectExistsAtZero_newRevisionIsTwo_throwsVe() {
- save("melancholy", DateTime.parse("1984-12-18TZ"), FULL, 0);
- VerifyException thrown =
+ save("melancholy", fakeClock.nowUtc(), FULL, 0);
+ IllegalArgumentException thrown =
assertThrows(
- VerifyException.class,
- () ->
- tm().transact(
- () -> saveRevision("melancholy", DateTime.parse("1984-12-18TZ"), FULL, 2)));
- assertThat(thrown).hasMessageThat().contains("should be at 1 ");
+ IllegalArgumentException.class,
+ () -> tm().transact(() -> saveRevision("melancholy", fakeClock.nowUtc(), FULL, 2)));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("RDE revision object should be at revision 1 but was");
}
- @Test
+ @TestTemplate
void testSaveRevision_negativeRevision_throwsIae() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
- () ->
- tm().transact(
- () ->
- saveRevision("melancholy", DateTime.parse("1984-12-18TZ"), FULL, -1)));
+ () -> tm().transact(() -> saveRevision("melancholy", fakeClock.nowUtc(), FULL, -1)));
assertThat(thrown).hasMessageThat().contains("Negative revision");
}
- @Test
+ @TestTemplate
void testSaveRevision_callerNotInTransaction_throwsIse() {
IllegalStateException thrown =
assertThrows(
- IllegalStateException.class,
- () -> saveRevision("frenzy", DateTime.parse("1984-12-18TZ"), FULL, 1));
+ IllegalStateException.class, () -> saveRevision("frenzy", fakeClock.nowUtc(), FULL, 1));
assertThat(thrown).hasMessageThat().contains("transaction");
}
public static void save(String tld, DateTime date, RdeMode mode, int revision) {
String triplet = RdeNamingUtils.makePartialName(tld, date, mode);
- RdeRevision object = new RdeRevision();
- object.id = triplet;
- object.revision = revision;
- ofy().saveWithoutBackup().entity(object).now();
+ RdeRevision object = RdeRevision.create(triplet, tld, date.toLocalDate(), mode, revision);
+ tm().transact(() -> tm().put(object));
}
}
diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java
index 5f5df6983..22757d034 100644
--- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java
+++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java
@@ -24,6 +24,7 @@ import google.registry.model.history.ContactHistoryTest;
import google.registry.model.history.DomainHistoryTest;
import google.registry.model.history.HostHistoryTest;
import google.registry.model.poll.PollMessageTest;
+import google.registry.model.rde.RdeRevisionTest;
import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.model.registry.RegistryTest;
import google.registry.model.registry.label.ReservedListSqlDaoTest;
@@ -86,6 +87,7 @@ import org.junit.runner.RunWith;
LockDaoTest.class,
PollMessageTest.class,
PremiumListDaoTest.class,
+ RdeRevisionTest.class,
RegistrarDaoTest.class,
RegistryTest.class,
ReservedListSqlDaoTest.class,
diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt
index eaae1ad5c..579806eb9 100644
--- a/core/src/test/resources/google/registry/model/schema.txt
+++ b/core/src/test/resources/google/registry/model/schema.txt
@@ -523,6 +523,7 @@ class google.registry.model.poll.PollMessage$OneTime {
}
class google.registry.model.rde.RdeRevision {
@Id java.lang.String id;
+ google.registry.model.UpdateAutoTimestamp updateTimestamp;
int revision;
}
class google.registry.model.registrar.Registrar {
diff --git a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html
index fc9083f0f..d26bf52e5 100644
--- a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html
+++ b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html
@@ -261,19 +261,19 @@ td.section {
generated on |
- 2020-10-19 18:49:22.440463 |
+ 2020-10-21 14:40:52.221127 |
last flyway file |
- V65__local_date_date_type.sql |
+ V66__create_rde_revision.sql |
-