diff --git a/core/src/main/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml b/core/src/main/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml index 9a8e71364..d08709338 100644 --- a/core/src/main/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml +++ b/core/src/main/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml @@ -25,6 +25,11 @@ + + + + + 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 1e69c0a01..0565f3161 100644 --- a/core/src/main/java/google/registry/model/domain/DomainBase.java +++ b/core/src/main/java/google/registry/model/domain/DomainBase.java @@ -16,7 +16,6 @@ package google.registry.model.domain; import com.googlecode.objectify.Key; -import com.googlecode.objectify.annotation.Entity; import google.registry.model.EppResource; import google.registry.model.EppResource.ForeignKeyedEppResource; import google.registry.model.annotations.ExternalMessagingName; @@ -31,10 +30,13 @@ import javax.persistence.AccessType; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.ElementCollection; +import javax.persistence.Entity; import javax.persistence.FetchType; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; +import javax.persistence.Table; import org.joda.time.DateTime; /** @@ -47,16 +49,17 @@ import org.joda.time.DateTime; * @see RFC 5731 */ @ReportedOn -@Entity -@javax.persistence.Entity(name = "Domain") -@javax.persistence.Table( +@com.googlecode.objectify.annotation.Entity +@Entity(name = "Domain") +@Table( name = "Domain", indexes = { - @javax.persistence.Index(columnList = "creationTime"), - @javax.persistence.Index(columnList = "currentSponsorRegistrarId"), - @javax.persistence.Index(columnList = "deletionTime"), - @javax.persistence.Index(columnList = "domainName"), - @javax.persistence.Index(columnList = "tld") + @Index(columnList = "creationTime"), + @Index(columnList = "currentSponsorRegistrarId"), + @Index(columnList = "deletionTime"), + @Index(columnList = "domainName"), + @Index(columnList = "tld"), + @Index(columnList = "autorenewEndTime") }) @WithStringVKey @ExternalMessagingName("domain") diff --git a/core/src/main/java/google/registry/model/domain/DomainContent.java b/core/src/main/java/google/registry/model/domain/DomainContent.java index ac8ba30a5..e48ebf459 100644 --- a/core/src/main/java/google/registry/model/domain/DomainContent.java +++ b/core/src/main/java/google/registry/model/domain/DomainContent.java @@ -252,11 +252,27 @@ public class DomainContent extends EppResource */ DateTime lastTransferTime; + /** + * When the domain's autorenewal status will expire. + * + *

This will be null for the vast majority of domains because all domains autorenew + * indefinitely by default and autorenew can only be countermanded by administrators, typically + * for reasons of the URS process or termination of a registrar for nonpayment. + * + *

When a domain is scheduled to not autorenew, this field is set to the current value of its + * {@link #registrationExpirationTime}, after which point the next invocation of a periodic + * cronjob will explicitly delete the domain. This field is a DateTime and not a boolean because + * of edge cases that occur during the autorenew grace period. We need to be able to tell the + * difference domains that have reached their life and must be deleted now, and domains that + * happen to be in the autorenew grace period now but should be deleted in roughly a year. + */ + @Nullable @Index DateTime autorenewEndTime; + @OnLoad void load() { // Reconstitute all of the contacts so that they have VKeys. allContacts = - allContacts.stream().map(contact -> contact.reconstitute()).collect(toImmutableSet()); + allContacts.stream().map(DesignatedContact::reconstitute).collect(toImmutableSet()); setContactFields(allContacts, true); // We have to return the cloned object here because the original object's @@ -275,8 +291,7 @@ public class DomainContent extends EppResource @PostLoad void postLoad() { // Reconstitute the contact list. - ImmutableSet.Builder contactsBuilder = - new ImmutableSet.Builder(); + ImmutableSet.Builder contactsBuilder = new ImmutableSet.Builder<>(); if (registrantContact != null) { contactsBuilder.add( @@ -323,6 +338,10 @@ public class DomainContent extends EppResource return smdId; } + public Optional getAutorenewEndTime() { + return Optional.ofNullable(autorenewEndTime); + } + @Override public DomainTransferData getTransferData() { return Optional.ofNullable(transferData).orElse(DomainTransferData.EMPTY); @@ -835,6 +854,11 @@ public class DomainContent extends EppResource return thisCastToDerived(); } + public B setAutorenewEndTime(Optional autorenewEndTime) { + getInstance().autorenewEndTime = autorenewEndTime.orElse(null); + return thisCastToDerived(); + } + @Override public B setTransferData(DomainTransferData transferData) { getInstance().transferData = transferData; diff --git a/core/src/main/java/google/registry/tools/GetSchemaCommand.java b/core/src/main/java/google/registry/tools/GetSchemaCommand.java index 54b5c9c29..5e1c628fc 100644 --- a/core/src/main/java/google/registry/tools/GetSchemaCommand.java +++ b/core/src/main/java/google/registry/tools/GetSchemaCommand.java @@ -14,14 +14,33 @@ package google.registry.tools; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import google.registry.model.SchemaVersion; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; /** Generates the schema file used for model versioning. */ @Parameters(commandDescription = "Generate a model schema file") final class GetSchemaCommand implements Command { + + @Parameter( + names = {"-o", "--out_file"}, + description = "Name of the output file.") + String outFile; + @Override - public void run() { - System.out.println(SchemaVersion.getSchema()); + public void run() throws IOException { + String schema = SchemaVersion.getSchema(); + if (outFile == null) { + System.out.println(schema); + } else { + File file = new File(outFile); + file.createNewFile(); // Create the output file if it doesn't already exist. + Files.write(file.toPath(), schema.getBytes(UTF_8)); + } } } diff --git a/core/src/test/java/google/registry/model/domain/DomainBaseTest.java b/core/src/test/java/google/registry/model/domain/DomainBaseTest.java index 864e54d13..1e069c9cf 100644 --- a/core/src/test/java/google/registry/model/domain/DomainBaseTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainBaseTest.java @@ -55,6 +55,7 @@ import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferStatus; import google.registry.persistence.VKey; +import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; @@ -66,12 +67,11 @@ public class DomainBaseTest extends EntityTestCase { private DomainBase domain; private VKey oneTimeBillKey; private VKey recurringBillKey; - private VKey domainKey; @BeforeEach void setUp() { createTld("com"); - domainKey = VKey.from(Key.create(null, DomainBase.class, "4-COM")); + VKey domainKey = VKey.from(Key.create(null, DomainBase.class, "4-COM")); VKey hostKey = persistResource( new HostResource.Builder() @@ -158,6 +158,7 @@ public class DomainBaseTest extends EntityTestCase { fakeClock.nowUtc().plusDays(1), "registrar", null)) + .setAutorenewEndTime(Optional.of(fakeClock.nowUtc().plusYears(2))) .build())); } @@ -176,7 +177,8 @@ public class DomainBaseTest extends EntityTestCase { "nsHosts", "currentSponsorClientId", "deletionTime", - "tld"); + "tld", + "autorenewEndTime"); } @Test diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt index 304d36eed..1b3667043 100644 --- a/core/src/test/resources/google/registry/model/schema.txt +++ b/core/src/test/resources/google/registry/model/schema.txt @@ -185,6 +185,7 @@ class google.registry.model.domain.DomainBase { java.util.Set status; java.util.Set> nsHosts; java.util.Set subordinateHosts; + org.joda.time.DateTime autorenewEndTime; org.joda.time.DateTime deletionTime; org.joda.time.DateTime lastEppUpdateTime; org.joda.time.DateTime lastTransferTime; @@ -765,4 +766,4 @@ enum google.registry.model.transfer.TransferStatus { PENDING; SERVER_APPROVED; SERVER_CANCELLED; -} +} \ No newline at end of file diff --git a/db/src/main/resources/sql/flyway/V48__domain_add_autorenew_end_time_column.sql b/db/src/main/resources/sql/flyway/V48__domain_add_autorenew_end_time_column.sql new file mode 100644 index 000000000..50ee6d2cd --- /dev/null +++ b/db/src/main/resources/sql/flyway/V48__domain_add_autorenew_end_time_column.sql @@ -0,0 +1,17 @@ +-- 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. + +ALTER TABLE "Domain" ADD COLUMN autorenew_end_time timestamptz; +ALTER TABLE "DomainHistory" ADD COLUMN autorenew_end_time timestamptz; +CREATE INDEX IDXlrq7v63pc21uoh3auq6eybyhl ON "Domain" (autorenew_end_time); diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index 4c6ff4c6a..4c22b0cb4 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -233,6 +233,7 @@ create sequence history_id_sequence start 1 increment 1; auth_info_repo_id text, auth_info_value text, billing_recurrence_id int8, + autorenew_end_time timestamptz, autorenew_poll_message_id int8, billing_contact text, deletion_poll_message_id int8, @@ -283,6 +284,7 @@ create sequence history_id_sequence start 1 increment 1; auth_info_repo_id text, auth_info_value text, billing_recurrence_id int8, + autorenew_end_time timestamptz, autorenew_poll_message_id int8, billing_contact text, deletion_poll_message_id int8, @@ -591,6 +593,7 @@ create index IDXhsjqiy2lyobfymplb28nm74lm on "Domain" (current_sponsor_registrar create index IDX5mnf0wn20tno4b9do88j61klr on "Domain" (deletion_time); create index IDXc5aw4pk1vkd6ymhvkpanmoadv on "Domain" (domain_name); create index IDXrwl38wwkli1j7gkvtywi9jokq on "Domain" (tld); +create index IDXlrq7v63pc21uoh3auq6eybyhl on "Domain" (autorenew_end_time); create index IDXrh4xmrot9bd63o382ow9ltfig on "DomainHistory" (creation_time); create index IDXaro1omfuaxjwmotk3vo00trwm on "DomainHistory" (history_registrar_id); create index IDXsu1nam10cjes9keobapn5jvxj on "DomainHistory" (history_type); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 69be2f619..1bdd3124a 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -401,7 +401,8 @@ CREATE TABLE public."Domain" ( update_timestamp timestamp with time zone, billing_recurrence_id bigint, autorenew_poll_message_id bigint, - deletion_poll_message_id bigint + deletion_poll_message_id bigint, + autorenew_end_time timestamp with time zone ); @@ -464,7 +465,8 @@ CREATE TABLE public."DomainHistory" ( last_epp_update_time timestamp with time zone, statuses text[], update_timestamp timestamp with time zone, - domain_repo_id text NOT NULL + domain_repo_id text NOT NULL, + autorenew_end_time timestamp with time zone ); @@ -1386,6 +1388,13 @@ CREATE INDEX idxkjt9yaq92876dstimd93hwckh ON public."Domain" USING btree (curren CREATE INDEX idxknk8gmj7s47q56cwpa6rmpt5l ON public."HostHistory" USING btree (history_type); +-- +-- Name: idxlrq7v63pc21uoh3auq6eybyhl; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxlrq7v63pc21uoh3auq6eybyhl ON public."Domain" USING btree (autorenew_end_time); + + -- -- Name: idxn1f711wicdnooa2mqb7g1m55o; Type: INDEX; Schema: public; Owner: - --