diff --git a/GEMINI.md b/GEMINI.md index 6a4488ab8..21452271b 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -7,10 +7,15 @@ This document outlines foundational mandates, architectural patterns, and projec ### 1. Rigorous Import Management - **Addition:** When adding new symbols, ensure the corresponding import is added. - **Removal:** When removing the last usage of a class or symbol from a file (e.g., removing a `@Inject Clock clock;` field), **immediately remove the associated import**. Do not wait for a build failure to identify unused imports. +- **No Redundant Qualifications:** NEVER use fully qualified class names (e.g., `java.time.temporal.ChronoUnit.DAYS`) in code when an import can be used instead. Always prefer adding an import and using the simple name. +- **Static Imports for DateTimeUtils:** Always statically import `toInstant` and `toDateTime` from `google.registry.util.DateTimeUtils`. NEVER write them as `DateTimeUtils.toInstant(...)` or `google.registry.util.DateTimeUtils.toInstant(...)` at the call site; use `toInstant(...)` and `toDateTime(...)` directly. - **Checkstyle:** Proactively fix common checkstyle errors (line length > 100, formatting, unused imports) during the initial code write. Do not wait for CI/build failures to address these, as iterative fixes are inefficient. -- **Verification:** Before finalizing any change, scan the imports section for redundancy. +- **Verification**: Before finalizing any change, scan the imports section for redundancy. +- **License Headers**: When creating new files, ensure the license header uses the current year (e.g., 2026). Existing files should retain their original year. -### 2. Time and Precision Handling (java.time Migration) +## 2. Time and Precision Handling (java.time Migration) + +- **Idiomatic java.time Usage:** Avoid redundant conversions between `Instant` and `DateTime`. If a field or parameter is an `Instant`, use it directly. Do not convert to `DateTime` just to call a deprecated method if an `Instant` alternative exists or can be easily created. - **Millisecond Precision:** Always truncate `Instant.now()` to milliseconds (using `.truncatedTo(ChronoUnit.MILLIS)`) to maintain consistency with Joda `DateTime` and the PostgreSQL schema (which enforces millisecond precision via JPA converters). - **Clock Injection:** - Avoid direct calls to `Instant.now()`, `DateTime.now()`, `ZonedDateTime.now()`, or `System.currentTimeMillis()`. @@ -50,3 +55,71 @@ This document outlines foundational mandates, architectural patterns, and projec ## Performance and Efficiency - **Turn Minimization:** Aim for "perfect" code in the first iteration. Iterative fixes for checkstyle or compilation errors consume significant context and time. - **Context Management:** Use sub-agents for batch refactoring or high-volume output tasks to keep the main session history lean and efficient. + +--- + +# Gemini Engineering Guide: Nomulus Codebase + +This document captures high-level architectural patterns, lessons learned from large-scale refactorings (like the Joda-Time to `java.time` migration), and specific instructions to avoid common pitfalls in this environment. + +## 🏛 Architecture Overview + +- **Transaction Management:** The codebase uses a custom wrapper around JPA. Always use `tm()` (from `TransactionManagerFactory`) to interact with the database. +- **Dependency Injection:** Dagger 2 is used extensively. If you see "cannot find symbol" errors for classes starting with `Dagger...`, the project is in a state where annotation processing failed. Fix compilation in core models first to restore generated code. +- **Value Types:** AutoValue and "ImmutableObject" patterns are dominant. Most models follow a `Buildable` pattern with a nested `Builder`. +- **Temporal Logic:** The project is migrating from Joda-Time to `java.time`. + - Core boundaries: `DateTimeUtils.START_OF_TIME_INSTANT` (Unix Epoch) and `END_OF_TIME_INSTANT` (Long.MAX_VALUE / 1000). + - Year Arithmetic: Use `DateTimeUtils.plusYears()` and `DateTimeUtils.minusYears()` to handle February 29th logic correctly. + +## Source Control +- **One Commit Per PR:** All changes for a single PR must be squashed into a single commit before merging. +- **Default to Amend:** Once an initial commit is created for a PR, all subsequent functional changes should be amended into that same commit by default (`git commit --amend --no-edit`). This ensures the PR remains a single, clean unit of work throughout the development lifecycle. +- **Commit Message Style:** Follow standard Git commit best practices. The subject line (first line) should be concise, capitalized, and **must not end with punctuation** (e.g., a period). +- **Final Validation:** Always run `git status` as the final step before declaring a task complete to ensure all changes are committed and the working directory is clean. +- **Commit Verification:** After any commit or amendment, explicitly verify the success of the operation (e.g., using `git status` and reviewing the diff). Never report a Git operation as "done" without having first successfully executed the command and confirmed the repository state. +- **Diff Review:** Before finalizing a task, review the full diff (e.g., `git diff HEAD^`) to ensure all changes are functional and relevant. Identify and revert any formatting-only changes in files that do not contain functional updates to keep the commit focused. + +## Refactoring & Migration Guardrails + + +### 1. Compiler Warnings are Errors (`-Werror`) +This project treats Error Prone warnings as errors. +- **`@InlineMeSuggester`**: When creating deprecated Joda-Time bridge methods (e.g., `getTimestamp() -> return toDateTime(getTimestampInstant())`), you **MUST** immediately add `@SuppressWarnings("InlineMeSuggester")`. If you don't, the build will fail. +- **Repeatable Annotations**: `@SuppressWarnings` is **NOT** repeatable in this environment. If a method or class already has a suppression (e.g., `@SuppressWarnings("unchecked")`), you must merge them: + - ❌ `@SuppressWarnings("unchecked") @SuppressWarnings("InlineMeSuggester")` + - ✅ `@SuppressWarnings({"unchecked", "InlineMeSuggester"})` + +### 2. Resolving Ambiguity +- **Null Overloads**: Adding an `Instant` overload to a method that previously took `DateTime` will break all `create(null)` calls. You must cast them: `create((Instant) null)`. +- **Type Erasure**: Methods taking `Optional` and `Optional` will clash due to erasure. Use distinct names, e.g., `setAutorenewEndTimeInstant(Optional time)`. + +### 3. Build Strategy +- **Surgical Changes**: In large-scale migrations, focus on "leaf" nodes first (Utilities -> Models -> Flows -> Actions). +- **PR Size**: Minimize PR size by retaining Joda-Time bridge methods for high-level "Action" and "Flow" classes unless a full migration is requested. Reverting changes to DNS and Reporting logic while updating the underlying models is a valid strategy to keep PRs reviewable. +- **Validation**: Always run `./gradlew build -x test` before attempting to run unit tests. Unit tests will not run if there are compilation errors in any part of the `core` module. Before finalizing a PR or declaring a task done, you MUST run the entire build using `./gradlew build` and verify that it succeeds completely without errors. Do not declare success if formatting checks (e.g., `spotlessCheck` or `javaIncrementalFormatCheck`) or tests fail. If formatting fails, run `./gradlew spotlessApply` and then re-run `./gradlew build` to verify everything passes. + +## 🚫 Common Pitfalls to Avoid + +- **Do not go in circles with the build:** If you see an `InlineMeSuggester` error, apply the suppression to **ALL** similar methods in that file and related files in one turn. Do not fix them one by one. +- Dagger/AutoValue corruption: If you modify a builder or a component incorrectly, Dagger will fail to generate code, leading to hundreds of "cannot find symbol" errors. If this happens, `git checkout` the last working state of the specific file and re-apply changes more surgically. +- **`replace` tool context**: When using `replace` on large files (like `Tld.java` or `DomainBase.java`), provide significant surrounding context. These files have many similar method signatures (getters/setters) that can lead to incorrect replacements. + +--- + +# GitHub and Pull Request Protocol + +This protocol defines the standard for interacting with GitHub repositories and processing Pull Request (PR) feedback. + +## 1. Interaction via `gh` CLI +- **Primary Tool:** ALWAYS use the `gh` CLI for all GitHub-related operations (listing PRs, viewing PR content, checking status, adding comments). +- **Credential Safety:** Never expose tokens or credentials in shell commands. + +## 2. Processing PR Feedback +- **Systematic Review:** When asked to address PR comments, first fetch all comments using `gh pr view --json reviews,comments`. +- **Minimal Scope Expansion:** Address comments surgically. If a fix requires changes beyond a few lines or expands the PR's original scope significantly, DO NOT implement it without explicit user approval. Instead, report the issue to the user. +- **Verification:** After addressing feedback, run the full build (`./gradlew build`) and relevant tests to ensure no regressions were introduced. + +## 3. PR Lifecycle Management +- **One Commit Per PR:** Ensure all changes are squashed into a single, clean commit. Use `git commit --amend --no-edit` for follow-up fixes. +- **Clean Workspace:** Always run `git status` and verify the repository state before declaring a task complete. + diff --git a/common/src/main/java/google/registry/util/DateTimeUtils.java b/common/src/main/java/google/registry/util/DateTimeUtils.java index 0272d137f..b395c5c9a 100644 --- a/common/src/main/java/google/registry/util/DateTimeUtils.java +++ b/common/src/main/java/google/registry/util/DateTimeUtils.java @@ -22,6 +22,11 @@ import com.google.common.collect.Ordering; import java.sql.Date; import java.time.Instant; import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.SignStyle; +import java.time.temporal.ChronoField; import javax.annotation.Nullable; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -55,6 +60,38 @@ public abstract class DateTimeUtils { */ public static final Instant END_INSTANT = Instant.ofEpochMilli(Long.MAX_VALUE / 1000); + /** + * Standard ISO 8601 formatter with millisecond precision in UTC. + * + *

Example: {@code 2024-03-27T10:15:30.105Z} + * + *

Handles large/negative years by using a sign prefix if necessary, compatible with {@link + * Instant#parse}. + */ + public static final DateTimeFormatter ISO_8601_FORMATTER = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 10, SignStyle.NORMAL) + .appendPattern("-MM-dd'T'HH:mm:ss.SSS'Z'") + .toFormatter() + .withZone(ZoneOffset.UTC); + + /** + * Parses an ISO-8601 string to an {@link Instant}. + * + *

This method is lenient and supports both strings with and without millisecond precision + * (e.g. {@code 2024-03-27T10:15:30Z} and {@code 2024-03-27T10:15:30.105Z}). It also supports + * large years (e.g. {@code 294247-01-10T04:00:54.775Z}). + */ + public static Instant parseInstant(String timestamp) { + try { + // Try the standard millisecond precision format first. + return Instant.from(ISO_8601_FORMATTER.parse(timestamp)); + } catch (DateTimeParseException e) { + // Fall back to the standard ISO instant parser which handles varied precision. + return Instant.parse(timestamp); + } + } + /** Returns the earliest of a number of given {@link DateTime} instances. */ public static DateTime earliestOf(DateTime first, DateTime... rest) { return earliestDateTimeOf(Lists.asList(first, rest)); @@ -79,15 +116,26 @@ public abstract class DateTimeUtils { /** Returns the latest of a number of given {@link DateTime} instances. */ public static DateTime latestOf(DateTime first, DateTime... rest) { + return latestDateTimeOf(Lists.asList(first, rest)); + } + + /** Returns the latest of a number of given {@link Instant} instances. */ + public static Instant latestOf(Instant first, Instant... rest) { return latestOf(Lists.asList(first, rest)); } /** Returns the latest element in a {@link DateTime} iterable. */ - public static DateTime latestOf(Iterable dates) { + public static DateTime latestDateTimeOf(Iterable dates) { checkArgument(!Iterables.isEmpty(dates)); return Ordering.natural().max(dates); } + /** Returns the latest element in a {@link Instant} iterable. */ + public static Instant latestOf(Iterable instants) { + checkArgument(!Iterables.isEmpty(instants)); + return Ordering.natural().max(instants); + } + /** Returns whether the first {@link DateTime} is equal to or earlier than the second. */ public static boolean isBeforeOrAt(DateTime timeToCheck, DateTime timeToCompareTo) { return !timeToCheck.isAfter(timeToCompareTo); @@ -112,8 +160,7 @@ public abstract class DateTimeUtils { * Adds years to a date, in the {@code Duration} sense of semantic years. Use this instead of * {@link DateTime#plusYears} to ensure that we never end up on February 29. */ - @Deprecated - public static DateTime leapSafeAddYears(DateTime now, int years) { + public static DateTime plusYears(DateTime now, int years) { checkArgument(years >= 0); return years == 0 ? now : now.plusYears(1).plusYears(years - 1); } @@ -122,7 +169,7 @@ public abstract class DateTimeUtils { * Adds years to a date, in the {@code Duration} sense of semantic years. Use this instead of * {@link java.time.ZonedDateTime#plusYears} to ensure that we never end up on February 29. */ - public static Instant leapSafeAddYears(Instant now, long years) { + public static Instant plusYears(Instant now, long years) { checkArgument(years >= 0); return (years == 0) ? now @@ -133,8 +180,7 @@ public abstract class DateTimeUtils { * Subtracts years from a date, in the {@code Duration} sense of semantic years. Use this instead * of {@link DateTime#minusYears} to ensure that we never end up on February 29. */ - @Deprecated - public static DateTime leapSafeSubtractYears(DateTime now, int years) { + public static DateTime minusYears(DateTime now, int years) { checkArgument(years >= 0); return years == 0 ? now : now.minusYears(1).minusYears(years - 1); } @@ -143,13 +189,31 @@ public abstract class DateTimeUtils { * Subtracts years from a date, in the {@code Duration} sense of semantic years. Use this instead * of {@link java.time.ZonedDateTime#minusYears} to ensure that we never end up on February 29. */ - public static Instant leapSafeSubtractYears(Instant now, int years) { + public static Instant minusYears(Instant now, long years) { checkArgument(years >= 0); return (years == 0) ? now : now.atZone(ZoneOffset.UTC).minusYears(1).minusYears(years - 1).toInstant(); } + /** + * @deprecated Use {@link #plusYears(DateTime, int)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static DateTime leapSafeAddYears(DateTime now, int years) { + return plusYears(now, years); + } + + /** + * @deprecated Use {@link #minusYears(DateTime, int)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static DateTime leapSafeSubtractYears(DateTime now, int years) { + return minusYears(now, years); + } + public static Date toSqlDate(LocalDate localDate) { return new Date(localDate.toDateTimeAtStartOfDay().getMillis()); } @@ -176,10 +240,6 @@ public abstract class DateTimeUtils { return (instant == null) ? null : org.joda.time.Instant.ofEpochMilli(instant.toEpochMilli()); } - public static Instant plusYears(Instant instant, int years) { - return instant.atZone(ZoneOffset.UTC).plusYears(years).toInstant(); - } - public static Instant plusDays(Instant instant, int days) { return instant.atZone(ZoneOffset.UTC).plusDays(days).toInstant(); } diff --git a/common/src/main/java/google/registry/util/SystemClock.java b/common/src/main/java/google/registry/util/SystemClock.java index 7e4e7551c..8fc1c557a 100644 --- a/common/src/main/java/google/registry/util/SystemClock.java +++ b/common/src/main/java/google/registry/util/SystemClock.java @@ -18,6 +18,7 @@ import static org.joda.time.DateTimeZone.UTC; import jakarta.inject.Inject; import java.time.Instant; +import java.time.temporal.ChronoUnit; import javax.annotation.concurrent.ThreadSafe; import org.joda.time.DateTime; @@ -42,6 +43,6 @@ public class SystemClock implements Clock { // (which uses millisecond precision via DateTimeConverter). This prevents subtle comparison // bugs where a high-precision Instant would be considered "after" a truncated database // timestamp. - return Instant.now().truncatedTo(java.time.temporal.ChronoUnit.MILLIS); + return Instant.now().truncatedTo(ChronoUnit.MILLIS); } } diff --git a/common/src/testing/java/google/registry/testing/FakeClock.java b/common/src/testing/java/google/registry/testing/FakeClock.java index 656ec7109..fe1936608 100644 --- a/common/src/testing/java/google/registry/testing/FakeClock.java +++ b/common/src/testing/java/google/registry/testing/FakeClock.java @@ -49,6 +49,11 @@ public final class FakeClock implements Clock { setTo(startTime); } + /** Creates a FakeClock initialized to a specific time. */ + public FakeClock(Instant startTime) { + setTo(startTime); + } + /** Returns the current time. */ @Override public DateTime nowUtc() { @@ -89,6 +94,11 @@ public final class FakeClock implements Clock { currentTimeMillis.set(time.getMillis()); } + /** Sets the time to the specified instant. */ + public void setTo(Instant time) { + currentTimeMillis.set(time.toEpochMilli()); + } + /** Invokes {@link #setAutoIncrementStep} with one millisecond-step. */ public FakeClock setAutoIncrementByOneMilli() { return setAutoIncrementStep(Duration.millis(1)); diff --git a/config/presubmits.py b/config/presubmits.py index 27e1a13fa..d3256e78d 100644 --- a/config/presubmits.py +++ b/config/presubmits.py @@ -153,7 +153,7 @@ PRESUBMITS = { PresubmitCheck( r".*java\.util\.Date.*", "java", - {"/node_modules/", "JpaTransactionManagerImpl.java"}, + {"/node_modules/", "JpaTransactionManagerImpl.java", "DateTimeUtils.java"}, ): "Do not use java.util.Date. Use classes in java.time package instead.", PresubmitCheck( diff --git a/core/src/main/java/google/registry/batch/BatchModule.java b/core/src/main/java/google/registry/batch/BatchModule.java index 15ab1c295..b868b53bb 100644 --- a/core/src/main/java/google/registry/batch/BatchModule.java +++ b/core/src/main/java/google/registry/batch/BatchModule.java @@ -21,7 +21,7 @@ import static google.registry.request.RequestParameters.extractBooleanParameter; import static google.registry.request.RequestParameters.extractIntParameter; import static google.registry.request.RequestParameters.extractLongParameter; import static google.registry.request.RequestParameters.extractOptionalBooleanParameter; -import static google.registry.request.RequestParameters.extractOptionalDatetimeParameter; +import static google.registry.request.RequestParameters.extractOptionalInstantParameter; import static google.registry.request.RequestParameters.extractOptionalIntParameter; import static google.registry.request.RequestParameters.extractOptionalParameter; import static google.registry.request.RequestParameters.extractRequiredDatetimeParameter; @@ -41,6 +41,7 @@ import google.registry.request.OptionalJsonPayload; import google.registry.request.Parameter; import jakarta.inject.Named; import jakarta.servlet.http.HttpServletRequest; +import java.time.Instant; import java.util.List; import java.util.Optional; import org.joda.time.DateTime; @@ -121,14 +122,14 @@ public class BatchModule { @Provides @Parameter(ExpandBillingRecurrencesAction.PARAM_START_TIME) - static Optional provideStartTime(HttpServletRequest req) { - return extractOptionalDatetimeParameter(req, ExpandBillingRecurrencesAction.PARAM_START_TIME); + static Optional provideStartTime(HttpServletRequest req) { + return extractOptionalInstantParameter(req, ExpandBillingRecurrencesAction.PARAM_START_TIME); } @Provides @Parameter(ExpandBillingRecurrencesAction.PARAM_END_TIME) - static Optional provideEndTime(HttpServletRequest req) { - return extractOptionalDatetimeParameter(req, ExpandBillingRecurrencesAction.PARAM_END_TIME); + static Optional provideEndTime(HttpServletRequest req) { + return extractOptionalInstantParameter(req, ExpandBillingRecurrencesAction.PARAM_END_TIME); } @Provides diff --git a/core/src/main/java/google/registry/batch/CheckBulkComplianceAction.java b/core/src/main/java/google/registry/batch/CheckBulkComplianceAction.java index b8b60af05..a73ca2f79 100644 --- a/core/src/main/java/google/registry/batch/CheckBulkComplianceAction.java +++ b/core/src/main/java/google/registry/batch/CheckBulkComplianceAction.java @@ -15,7 +15,8 @@ package google.registry.batch; import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.minusYears; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -110,7 +111,8 @@ public class CheckBulkComplianceAction implements Runnable { + " 'DOMAIN_CREATE'", Long.class) .setParameter("token", bulkPricingPackage.getToken()) - .setParameter("lastBilling", bulkPricingPackage.getNextBillingDate().minusYears(1)) + .setParameter( + "lastBilling", minusYears(bulkPricingPackage.getNextBillingDateInstant(), 1)) .getSingleResult(); if (creates > bulkPricingPackage.getMaxCreates()) { long overage = creates - bulkPricingPackage.getMaxCreates(); @@ -127,7 +129,7 @@ public class CheckBulkComplianceAction implements Runnable { + " AND deletionTime = :endOfTime", Long.class) .setParameter("token", bulkPricingPackage.getToken()) - .setParameter("endOfTime", END_OF_TIME) + .setParameter("endOfTime", END_INSTANT) .getSingleResult(); if (activeDomains > bulkPricingPackage.getMaxDomains()) { int overage = Ints.saturatedCast(activeDomains) - bulkPricingPackage.getMaxDomains(); diff --git a/core/src/main/java/google/registry/batch/DeleteExpiredDomainsAction.java b/core/src/main/java/google/registry/batch/DeleteExpiredDomainsAction.java index a23d5ce17..10cc68c85 100644 --- a/core/src/main/java/google/registry/batch/DeleteExpiredDomainsAction.java +++ b/core/src/main/java/google/registry/batch/DeleteExpiredDomainsAction.java @@ -19,7 +19,6 @@ import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8; import static google.registry.flows.FlowUtils.marshalWithLenientRetry; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.END_INSTANT; -import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.ResourceUtils.readResourceUtf8; import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT; @@ -43,10 +42,10 @@ import google.registry.request.auth.Auth; import google.registry.request.lock.LockHandler; import google.registry.util.Clock; import jakarta.inject.Inject; +import java.time.Instant; import java.util.Optional; import java.util.concurrent.Callable; import java.util.logging.Level; -import org.joda.time.DateTime; import org.joda.time.Duration; /** @@ -125,7 +124,7 @@ public class DeleteExpiredDomainsAction implements Runnable { } private void runLocked() { - DateTime runTime = clock.nowUtc(); + Instant runTime = clock.now(); logger.atInfo().log( "Deleting non-renewing domains with autorenew end times up through %s.", runTime); @@ -134,7 +133,7 @@ public class DeleteExpiredDomainsAction implements Runnable { () -> tm().createQueryComposer(Domain.class) .where("autorenewEndTime", Comparator.LTE, runTime) - .where("deletionTime", Comparator.EQ, END_OF_TIME) + .where("deletionTime", Comparator.EQ, END_INSTANT) .list()); if (domainsToDelete.isEmpty()) { logger.atInfo().log("Found 0 domains to delete."); diff --git a/core/src/main/java/google/registry/batch/DeleteProberDataAction.java b/core/src/main/java/google/registry/batch/DeleteProberDataAction.java index f442f308a..2b27a0ba9 100644 --- a/core/src/main/java/google/registry/batch/DeleteProberDataAction.java +++ b/core/src/main/java/google/registry/batch/DeleteProberDataAction.java @@ -27,6 +27,7 @@ import static google.registry.request.Action.Method.POST; import static google.registry.request.RequestParameters.PARAM_BATCH_SIZE; import static google.registry.request.RequestParameters.PARAM_DRY_RUN; import static google.registry.request.RequestParameters.PARAM_TLDS; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.RegistryEnvironment.PRODUCTION; import com.google.common.base.Strings; @@ -188,8 +189,8 @@ public class DeleteProberDataAction implements Runnable { .setParameter("tlds", deletableTlds) .setParameter( "creationTimeCutoff", CreateAutoTimestamp.create(now.minus(DOMAIN_USED_DURATION))) - .setParameter("nowMinusSoftDeleteDelay", now.minus(SOFT_DELETE_DELAY)) - .setParameter("now", now); + .setParameter("nowMinusSoftDeleteDelay", toInstant(now.minus(SOFT_DELETE_DELAY))) + .setParameter("now", toInstant(now)); ImmutableList domainList = query.setMaxResults(batchSize).getResultStream().collect(toImmutableList()); ImmutableList.Builder domainRepoIdsToHardDelete = new ImmutableList.Builder<>(); diff --git a/core/src/main/java/google/registry/batch/ExpandBillingRecurrencesAction.java b/core/src/main/java/google/registry/batch/ExpandBillingRecurrencesAction.java index b2034c94d..c54091e92 100644 --- a/core/src/main/java/google/registry/batch/ExpandBillingRecurrencesAction.java +++ b/core/src/main/java/google/registry/batch/ExpandBillingRecurrencesAction.java @@ -19,7 +19,7 @@ import static google.registry.beam.BeamUtils.createJobName; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.request.RequestParameters.PARAM_DRY_RUN; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static jakarta.servlet.http.HttpServletResponse.SC_OK; @@ -42,8 +42,9 @@ import google.registry.util.Clock; import google.registry.util.RegistryEnvironment; import jakarta.inject.Inject; import java.io.IOException; +import java.time.Instant; +import java.util.Locale; import java.util.Optional; -import org.joda.time.DateTime; /** * An action that kicks off a {@link ExpandBillingRecurrencesPipeline} dataflow job to expand {@link @@ -74,11 +75,11 @@ public class ExpandBillingRecurrencesAction implements Runnable { @Inject @Parameter(PARAM_START_TIME) - Optional startTimeParam; + Optional startTimeParam; @Inject @Parameter(PARAM_END_TIME) - Optional endTimeParam; + Optional endTimeParam; @Inject @Config("projectId") @@ -102,16 +103,15 @@ public class ExpandBillingRecurrencesAction implements Runnable { @Override public void run() { checkArgument(!(isDryRun && advanceCursor), "Cannot advance the cursor in a dry run."); - DateTime endTime = endTimeParam.orElse(clock.nowUtc()); - checkArgument( - !endTime.isAfter(clock.nowUtc()), "End time (%s) must be at or before now", endTime); - DateTime startTime = + Instant endTime = endTimeParam.orElse(clock.now()); + checkArgument(!endTime.isAfter(clock.now()), "End time (%s) must be at or before now", endTime); + Instant startTime = startTimeParam.orElse( tm().transact( () -> tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING)) - .orElse(Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME)) - .getCursorTime())); + .orElse(Cursor.createGlobal(RECURRING_BILLING, START_INSTANT)) + .getCursorTimeInstant())); checkArgument( startTime.isBefore(endTime), String.format("Start time (%s) must be before end time (%s)", startTime, endTime)); @@ -120,15 +120,17 @@ public class ExpandBillingRecurrencesAction implements Runnable { .setJobName( createJobName( String.format( - "expand-billing-%s", startTime.toString("yyyy-MM-dd't'HH-mm-ss'z'")), + "expand-billing-%s", + startTime.toString().replace(':', '-').replace('.', '-')) + .toLowerCase(Locale.ROOT), clock)) .setContainerSpecGcsPath( String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME)) .setParameters( new ImmutableMap.Builder() .put("registryEnvironment", RegistryEnvironment.get().name()) - .put("startTime", startTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")) - .put("endTime", endTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")) + .put("startTime", startTime.toString()) + .put("endTime", endTime.toString()) .put("isDryRun", Boolean.toString(isDryRun)) .put("advanceCursor", Boolean.toString(advanceCursor)) .build()); diff --git a/core/src/main/java/google/registry/beam/billing/ExpandBillingRecurrencesPipeline.java b/core/src/main/java/google/registry/beam/billing/ExpandBillingRecurrencesPipeline.java index 1bfcc1c48..3cc58a332 100644 --- a/core/src/main/java/google/registry/beam/billing/ExpandBillingRecurrencesPipeline.java +++ b/core/src/main/java/google/registry/beam/billing/ExpandBillingRecurrencesPipeline.java @@ -21,10 +21,12 @@ import static google.registry.model.domain.Period.Unit.YEARS; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.union; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; import static google.registry.util.DateTimeUtils.earliestOf; import static google.registry.util.DateTimeUtils.latestOf; -import static google.registry.util.DateTimeUtils.toInstant; +import static google.registry.util.DateTimeUtils.minusYears; +import static google.registry.util.DateTimeUtils.plusYears; +import static google.registry.util.DateTimeUtils.toDateTime; import static org.apache.beam.sdk.values.TypeDescriptors.voids; import com.google.common.collect.ImmutableMap; @@ -54,6 +56,8 @@ import google.registry.util.Clock; import google.registry.util.SystemClock; import jakarta.inject.Singleton; import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; import java.util.Optional; import java.util.Set; import org.apache.beam.sdk.Pipeline; @@ -73,7 +77,6 @@ import org.apache.beam.sdk.transforms.Wait; import org.apache.beam.sdk.values.KV; import org.apache.beam.sdk.values.PCollection; import org.apache.beam.sdk.values.PDone; -import org.joda.time.DateTime; /** * Definition of a Dataflow Flex pipeline template, which expands {@link BillingRecurrence} to @@ -134,9 +137,9 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { } // Inclusive lower bound of the expansion window. - private final DateTime startTime; + private final Instant startTime; // Exclusive lower bound of the expansion window. - private final DateTime endTime; + private final Instant endTime; private final boolean isDryRun; private final boolean advanceCursor; private final Counter recurrencesInScopeCounter = @@ -151,10 +154,10 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { Metrics.counter("ExpandBilling", "OneTimes that would be expanded"); ExpandBillingRecurrencesPipeline(ExpandBillingRecurrencesPipelineOptions options, Clock clock) { - startTime = DateTime.parse(options.getStartTime()); - endTime = DateTime.parse(options.getEndTime()); + startTime = Instant.parse(options.getStartTime()); + endTime = Instant.parse(options.getEndTime()); checkArgument( - !endTime.isAfter(clock.nowUtc()), + !endTime.isAfter(clock.now()), String.format("End time %s must be at or before now.", endTime)); checkArgument( startTime.isBefore(endTime), @@ -199,7 +202,7 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { "startTime", startTime, "oneYearAgo", - endTime.minusYears(1)), + minusYears(endTime, 1)), true, (Long id) -> { recurrencesInScopeCounter.inc(); @@ -268,18 +271,18 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { // The best way to handle any unexpected behavior is to simply drop the recurrence from // expansion, if its new state still calls for an expansion, it would be picked up the next time // the pipeline runs. - ImmutableSet eventTimes; + ImmutableSet eventTimes; try { eventTimes = ImmutableSet.copyOf( billingRecurrence .getRecurrenceTimeOfYear() - .getInstancesInRange( + .getInstancesInRangeInstant( Range.closedOpen( latestOf( - billingRecurrence.getRecurrenceLastExpansion().plusYears(1), + plusYears(billingRecurrence.getRecurrenceLastExpansionInstant(), 1), startTime), - earliestOf(billingRecurrence.getRecurrenceEndTime(), endTime)))); + earliestOf(billingRecurrence.getRecurrenceEndTimeInstant(), endTime)))); } catch (IllegalArgumentException e) { return; } @@ -289,28 +292,29 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { // Find the times for which the OneTime billing event are already created, making this expansion // idempotent. There is no need to match to the domain repo ID as the cancellation matching // billing event itself can only be for a single domain. - ImmutableSet existingEventTimes = + ImmutableSet existingEventTimes = ImmutableSet.copyOf( tm().query( "SELECT eventTime FROM BillingEvent WHERE cancellationMatchingBillingEvent =" + " :key", - DateTime.class) + Instant.class) .setParameter("key", billingRecurrence.createVKey()) .getResultList()); - Set eventTimesToExpand = difference(eventTimes, existingEventTimes); + Set eventTimesToExpand = difference(eventTimes, existingEventTimes); if (eventTimesToExpand.isEmpty()) { return; } - DateTime recurrenceLastExpansionTime = billingRecurrence.getRecurrenceLastExpansion(); + Instant recurrenceLastExpansionTime = billingRecurrence.getRecurrenceLastExpansionInstant(); // Create new OneTime and DomainHistory for EventTimes that needs to be expanded. - for (DateTime eventTime : eventTimesToExpand) { + for (Instant eventTime : eventTimesToExpand) { recurrenceLastExpansionTime = latestOf(recurrenceLastExpansionTime, eventTime); oneTimesToExpandCounter.inc(); - DateTime billingTime = eventTime.plus(tld.getAutoRenewGracePeriodLength()); + Instant billingTime = + eventTime.plus(Duration.ofMillis(tld.getAutoRenewGracePeriodLength().getMillis())); // Note that the DomainHistory is created as of transaction time, as opposed to event time. // This might be counterintuitive because other DomainHistories are created at the time // mutation events occur, such as in DomainDeleteFlow or DomainRenewFlow. Therefore, it is @@ -337,7 +341,7 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { new DomainHistory.Builder() .setBySuperuser(false) .setRegistrarId(billingRecurrence.getRegistrarId()) - .setModificationTime(tm().getTransactionTime()) + .setModificationTime(tm().getTxTime()) .setDomain(domain) .setPeriod(Period.create(1, YEARS)) .setReason("Domain autorenewal by ExpandRecurringBillingEventsPipeline") @@ -373,7 +377,7 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { // during ARGP). // // See: DomainFlowUtils#createCancellingRecords - domain.getDeletionTime().isBefore(toInstant(billingTime)) + domain.getDeletionTime().isBefore(billingTime) ? ImmutableSet.of() : ImmutableSet.of( DomainTransactionRecord.create( @@ -398,7 +402,7 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { .getRenewPrice( tld, billingRecurrence.getTargetId(), - eventTime, + toDateTime(eventTime), 1, billingRecurrence, Optional.empty()) @@ -439,12 +443,12 @@ public class ExpandBillingRecurrencesPipeline implements Serializable { public void processElement() { tm().transact( () -> { - DateTime currentCursorTime = + Instant currentCursorTime = tm().loadByKeyIfPresent( Cursor.createGlobalVKey(RECURRING_BILLING)) .orElse( - Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME)) - .getCursorTime(); + Cursor.createGlobal(RECURRING_BILLING, START_INSTANT)) + .getCursorTimeInstant(); if (!currentCursorTime.equals(startTime)) { throw new IllegalStateException( String.format( diff --git a/core/src/main/java/google/registry/beam/rde/RdePipeline.java b/core/src/main/java/google/registry/beam/rde/RdePipeline.java index 3bc8f3d83..029ceb251 100644 --- a/core/src/main/java/google/registry/beam/rde/RdePipeline.java +++ b/core/src/main/java/google/registry/beam/rde/RdePipeline.java @@ -27,6 +27,7 @@ import static google.registry.beam.rde.RdePipeline.TupleTags.REVISION_ID; import static google.registry.beam.rde.RdePipeline.TupleTags.SUPERORDINATE_DOMAINS; import static google.registry.model.reporting.HistoryEntryDao.RESOURCE_TYPES_TO_HISTORY_TYPES; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.SafeSerializationUtils.safeDeserializeCollection; import static google.registry.util.SafeSerializationUtils.serializeCollection; import static google.registry.util.SerializeUtils.decodeBase64; @@ -314,17 +315,16 @@ public class RdePipeline implements Serializable { String.format("Load most recent %s", historyClass.getSimpleName()), RegistryJpaIO.read( ("SELECT repoId, revisionId FROM %entity% WHERE (repoId, modificationTime) IN" - + " (SELECT repoId, MAX(modificationTime) FROM %entity% WHERE" - + " modificationTime <= :watermark GROUP BY repoId) AND resource.deletionTime" - + " > :watermark AND COALESCE(resource.creationRegistrarId, '') NOT LIKE" - + " 'prober-%' AND COALESCE(resource.currentSponsorRegistrarId, '') NOT LIKE" - + " 'prober-%' AND COALESCE(resource.lastEppUpdateRegistrarId, '') NOT LIKE" - + " 'prober-%' " + + " (SELECT repoId, MAX(modificationTime) FROM %entity% WHERE modificationTime" + + " <= :watermark GROUP BY repoId) AND resource.deletionTime > :watermark AND" + + " COALESCE(resource.creationRegistrarId, '') NOT LIKE 'prober-%' AND" + + " COALESCE(resource.currentSponsorRegistrarId, '') NOT LIKE 'prober-%' AND" + + " COALESCE(resource.lastEppUpdateRegistrarId, '') NOT LIKE 'prober-%' " + (historyClass == DomainHistory.class ? "AND resource.tld IN " + "(SELECT id FROM Tld WHERE tldType = 'REAL')" : "")) .replace("%entity%", historyClass.getSimpleName()), - ImmutableMap.of("watermark", watermark), + ImmutableMap.of("watermark", toInstant(watermark)), Object[].class, row -> KV.of((String) row[0], (long) row[1])) .withCoder(KvCoder.of(StringUtf8Coder.of(), VarLongCoder.of()))); diff --git a/core/src/main/java/google/registry/beam/resave/ResaveAllEppResourcesPipeline.java b/core/src/main/java/google/registry/beam/resave/ResaveAllEppResourcesPipeline.java index 2fd8f3cbd..0185433a2 100644 --- a/core/src/main/java/google/registry/beam/resave/ResaveAllEppResourcesPipeline.java +++ b/core/src/main/java/google/registry/beam/resave/ResaveAllEppResourcesPipeline.java @@ -16,6 +16,7 @@ package google.registry.beam.resave; import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.END_INSTANT; import static org.apache.beam.sdk.values.TypeDescriptors.integers; import com.google.common.collect.ImmutableList; @@ -30,7 +31,6 @@ import google.registry.model.domain.DomainBase; import google.registry.model.host.Host; import google.registry.persistence.PersistenceModule.TransactionIsolationLevel; import google.registry.persistence.VKey; -import google.registry.util.DateTimeUtils; import java.io.Serializable; import org.apache.beam.sdk.Pipeline; import org.apache.beam.sdk.PipelineResult; @@ -74,7 +74,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable { "SELECT repoId FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND" + " d.transferData.pendingTransferExpirationTime < current_timestamp()) OR" + " (d.registrationExpirationTime < current_timestamp() AND d.deletionTime =" - + " (:END_OF_TIME)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId =" + + " (:END_INSTANT)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId =" + " d.repoId AND gp.expirationTime < current_timestamp()))"; private final ResaveAllEppResourcesPipelineOptions options; @@ -108,7 +108,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable { Read repoIdRead = RegistryJpaIO.read( DOMAINS_TO_PROJECT_QUERY, - ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME), + ImmutableMap.of("END_INSTANT", END_INSTANT), String.class, r -> r) .withCoder(StringUtf8Coder.of()); diff --git a/core/src/main/java/google/registry/bsa/BsaRefreshAction.java b/core/src/main/java/google/registry/bsa/BsaRefreshAction.java index 3ba22b18a..3fd6df3af 100644 --- a/core/src/main/java/google/registry/bsa/BsaRefreshAction.java +++ b/core/src/main/java/google/registry/bsa/BsaRefreshAction.java @@ -36,9 +36,9 @@ import google.registry.request.auth.Auth; import google.registry.util.BatchedStreams; import google.registry.util.Clock; import jakarta.inject.Inject; +import java.time.Duration; import java.util.Optional; import java.util.stream.Stream; -import org.joda.time.Duration; @Action( service = Action.Service.BACKEND, @@ -67,7 +67,7 @@ public class BsaRefreshAction implements Runnable { GcsClient gcsClient, BsaReportSender bsaReportSender, @Config("bsaTxnBatchSize") int transactionBatchSize, - @Config("domainCreateTxnCommitTimeLag") Duration domainCreateTxnCommitTimeLag, + @Config("domainCreateTxnCommitTimeLag") org.joda.time.Duration domainCreateTxnCommitTimeLag, BsaEmailSender emailSender, BsaLock bsaLock, Clock clock, @@ -76,7 +76,7 @@ public class BsaRefreshAction implements Runnable { this.gcsClient = gcsClient; this.bsaReportSender = bsaReportSender; this.transactionBatchSize = transactionBatchSize; - this.domainCreateTxnCommitTimeLag = domainCreateTxnCommitTimeLag; + this.domainCreateTxnCommitTimeLag = Duration.ofMillis(domainCreateTxnCommitTimeLag.getMillis()); this.emailSender = emailSender; this.bsaLock = bsaLock; this.clock = clock; @@ -103,7 +103,7 @@ public class BsaRefreshAction implements Runnable { /** Executes the refresh action while holding the BSA lock. */ Void runWithinLock() { // Cannot enroll new TLDs after download starts. This may change if b/309175410 is fixed. - if (!Tlds.hasActiveBsaEnrollment(clock.nowUtc())) { + if (!Tlds.hasActiveBsaEnrollment(clock.now())) { logger.atInfo().log("No TLDs enrolled with BSA. Quitting."); return null; } @@ -116,7 +116,7 @@ public class BsaRefreshAction implements Runnable { DomainsRefresher refresher = new DomainsRefresher( schedule.prevRefreshTime(), - clock.nowUtc(), + clock.now(), domainCreateTxnCommitTimeLag, transactionBatchSize); switch (schedule.stage()) { diff --git a/core/src/main/java/google/registry/bsa/BsaValidateAction.java b/core/src/main/java/google/registry/bsa/BsaValidateAction.java index a46b2c53f..23e7df7e8 100644 --- a/core/src/main/java/google/registry/bsa/BsaValidateAction.java +++ b/core/src/main/java/google/registry/bsa/BsaValidateAction.java @@ -57,11 +57,11 @@ import google.registry.request.Response; import google.registry.request.auth.Auth; import google.registry.util.Clock; import jakarta.inject.Inject; +import java.time.Duration; +import java.time.Instant; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; -import org.joda.time.DateTime; -import org.joda.time.Duration; /** Validates the BSA data in the database against the most recent block lists. */ @Action( @@ -88,14 +88,14 @@ public class BsaValidateAction implements Runnable { IdnChecker idnChecker, BsaEmailSender emailSender, @Config("bsaTxnBatchSize") int transactionBatchSize, - @Config("bsaValidationMaxStaleness") Duration maxStaleness, + @Config("bsaValidationMaxStaleness") org.joda.time.Duration maxStaleness, Clock clock, Response response) { this.gcsClient = gcsClient; this.idnChecker = idnChecker; this.emailSender = emailSender; this.transactionBatchSize = transactionBatchSize; - this.maxStaleness = maxStaleness; + this.maxStaleness = Duration.ofMillis(maxStaleness.getMillis()); this.clock = clock; this.response = response; } @@ -186,7 +186,7 @@ public class BsaValidateAction implements Runnable { ForeignKeyUtils.loadResources( Domain.class, batch.stream().map(UnblockableDomain::domainName).collect(toImmutableList()), - clock.nowUtc()); + clock.now()); for (var unblockable : batch) { verifyDomainStillUnblockableWithReason(unblockable, activeDomains).ifPresent(errors::add); } @@ -199,7 +199,7 @@ public class BsaValidateAction implements Runnable { Optional verifyDomainStillUnblockableWithReason( UnblockableDomain domain, ImmutableMap activeDomains) { - DateTime now = clock.nowUtc(); + Instant now = clock.now(); boolean isRegistered = activeDomains.containsKey(domain.domainName()); boolean isReserved = isReservedDomain(domain.domainName(), now); InternetDomainName domainName = InternetDomainName.from(domain.domainName()); @@ -228,7 +228,7 @@ public class BsaValidateAction implements Runnable { } boolean isStalenessAllowed(Domain domain) { - return domain.getCreationTime().plus(maxStaleness).isAfter(clock.nowUtc()); + return domain.getCreationTimeInstant().plus(maxStaleness).isAfter(clock.now()); } /** Returns unique labels across all block lists in the download specified by {@code jobName}. */ @@ -262,20 +262,20 @@ public class BsaValidateAction implements Runnable { } ImmutableList checkMissingUnblockableDomains() { - DateTime now = clock.nowUtc(); + Instant now = clock.now(); ImmutableList.Builder errors = new ImmutableList.Builder<>(); errors.addAll(checkForMissingReservedUnblockables(now)); errors.addAll(checkForMissingRegisteredUnblockables(now)); return errors.build(); } - ImmutableList checkForMissingRegisteredUnblockables(DateTime now) { + ImmutableList checkForMissingRegisteredUnblockables(Instant now) { ImmutableList.Builder errors = new ImmutableList.Builder<>(); ImmutableList bsaEnabledTlds = getTldEntitiesOfType(TldType.REAL).stream() .filter(tld -> isEnrolledWithBsa(tld, now)) .collect(toImmutableList()); - DateTime stalenessThreshold = now.minus(maxStaleness); + Instant stalenessThreshold = now.minus(maxStaleness); bsaEnabledTlds.stream() .map(Tld::getTldStr) .map(tld -> bsaQuery(() -> queryMissedRegisteredUnblockables(tld, now))) @@ -290,7 +290,7 @@ public class BsaValidateAction implements Runnable { return errors.build(); } - ImmutableList checkForMissingReservedUnblockables(DateTime now) { + ImmutableList checkForMissingReservedUnblockables(Instant now) { ImmutableList.Builder errors = new ImmutableList.Builder<>(); try (Stream> reservedNames = toBatches( diff --git a/core/src/main/java/google/registry/bsa/ReservedDomainsUtils.java b/core/src/main/java/google/registry/bsa/ReservedDomainsUtils.java index 0aa20bc79..145f5d612 100644 --- a/core/src/main/java/google/registry/bsa/ReservedDomainsUtils.java +++ b/core/src/main/java/google/registry/bsa/ReservedDomainsUtils.java @@ -20,6 +20,7 @@ import static google.registry.bsa.BsaStringUtils.DOMAIN_JOINER; import static google.registry.flows.domain.DomainFlowUtils.isReserved; import static google.registry.model.tld.Tlds.findTldForName; import static google.registry.model.tld.label.ReservedList.loadReservedLists; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableSet; import com.google.common.net.InternetDomainName; @@ -28,6 +29,7 @@ import google.registry.model.tld.Tld.TldState; import google.registry.model.tld.Tld.TldType; import google.registry.model.tld.Tlds; import google.registry.model.tld.label.ReservedList; +import java.time.Instant; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -45,7 +47,16 @@ public final class ReservedDomainsUtils { private ReservedDomainsUtils() {} + /** + * @deprecated Use {@link #getAllReservedNames(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static Stream getAllReservedNames(DateTime now) { + return getAllReservedNames(toInstant(now)); + } + + public static Stream getAllReservedNames(Instant now) { return Tlds.getTldEntitiesOfType(TldType.REAL).stream() .filter(tld -> Tld.isEnrolledWithBsa(tld, now)) .map(tld -> getAllReservedDomainsInTld(tld, now)) @@ -53,7 +64,7 @@ public final class ReservedDomainsUtils { } /** Returns all reserved domains in a given {@code tld} as of {@code now}. */ - static ImmutableSet getAllReservedDomainsInTld(Tld tld, DateTime now) { + static ImmutableSet getAllReservedDomainsInTld(Tld tld, Instant now) { return loadReservedLists(tld.getReservedListNames()).stream() .map(ReservedList::getReservedListEntries) .map(Map::keySet) @@ -63,11 +74,20 @@ public final class ReservedDomainsUtils { .collect(toImmutableSet()); } + /** + * @deprecated Use {@link #getAllReservedDomainsInTld(Tld, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + static ImmutableSet getAllReservedDomainsInTld(Tld tld, DateTime now) { + return getAllReservedDomainsInTld(tld, toInstant(now)); + } + /** * Returns true if {@code domain} is a reserved name that can be registered right now (e.g., * during sunrise or with allocation token), therefore unblockable. */ - public static boolean isReservedDomain(String domain, DateTime now) { + public static boolean isReservedDomain(String domain, Instant now) { Optional tldStr = findTldForName(InternetDomainName.from(domain)); verify(tldStr.isPresent(), "Tld for domain [%s] unexpectedly missing.", domain); Tld tld = Tld.get(tldStr.get().toString()); @@ -75,4 +95,13 @@ public final class ReservedDomainsUtils { InternetDomainName.from(domain), Objects.equals(tld.getTldState(now), TldState.START_DATE_SUNRISE)); } + + /** + * @deprecated Use {@link #isReservedDomain(String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static boolean isReservedDomain(String domain, DateTime now) { + return isReservedDomain(domain, toInstant(now)); + } } diff --git a/core/src/main/java/google/registry/bsa/UploadBsaUnavailableDomainsAction.java b/core/src/main/java/google/registry/bsa/UploadBsaUnavailableDomainsAction.java index c2317ea9d..b96dafd53 100644 --- a/core/src/main/java/google/registry/bsa/UploadBsaUnavailableDomainsAction.java +++ b/core/src/main/java/google/registry/bsa/UploadBsaUnavailableDomainsAction.java @@ -54,6 +54,7 @@ import java.io.OutputStreamWriter; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.Writer; +import java.time.Instant; import java.util.Optional; import java.util.zip.GZIPOutputStream; import okhttp3.MediaType; @@ -65,7 +66,6 @@ import okhttp3.Response; import okio.BufferedSink; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.joda.time.DateTime; /** * Daily action that uploads unavailable domain names on applicable TLDs to BSA. @@ -122,7 +122,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable { public void run() { // TODO(mcilwain): Implement a date Cursor, have the cronjob run frequently, and short-circuit // the run if the daily upload is already completed. - DateTime runTime = clock.nowUtc(); + Instant runTime = clock.now(); ImmutableSortedSet unavailableDomains = getUnavailableDomains(runTime); if (unavailableDomains.isEmpty()) { logger.atWarning().log("No unavailable domains found; terminating."); @@ -141,7 +141,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable { } /** Uploads the unavailable domains list to GCS in the unavailable domains bucket. */ - boolean uploadToGcs(ImmutableSortedSet unavailableDomains, DateTime runTime) { + boolean uploadToGcs(ImmutableSortedSet unavailableDomains, Instant runTime) { logger.atInfo().log("Uploading unavailable names file to GCS in bucket %s", gcsBucket); BlobId blobId = BlobId.of(gcsBucket, createFilename(runTime)); // `gcsUtils.openOutputStream` returns a buffered stream @@ -159,7 +159,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable { } } - boolean uploadToBsa(ImmutableSortedSet unavailableDomains, DateTime runTime) { + boolean uploadToBsa(ImmutableSortedSet unavailableDomains, Instant runTime) { try { Hasher sha512Hasher = Hashing.sha512().newHasher(); unavailableDomains.stream() @@ -211,11 +211,11 @@ public class UploadBsaUnavailableDomainsAction implements Runnable { } } - private static String createFilename(DateTime runTime) { + private static String createFilename(Instant runTime) { return String.format("unavailable_domains_%s.txt", runTime.toString()); } - private ImmutableSortedSet getUnavailableDomains(DateTime runTime) { + private ImmutableSortedSet getUnavailableDomains(Instant runTime) { // Get list of TLDs to process. ImmutableSet bsaEnabledTlds = getTldEntitiesOfType(TldType.REAL).stream() diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaDomainRefresh.java b/core/src/main/java/google/registry/bsa/persistence/BsaDomainRefresh.java index 47718d745..b75de3a80 100644 --- a/core/src/main/java/google/registry/bsa/persistence/BsaDomainRefresh.java +++ b/core/src/main/java/google/registry/bsa/persistence/BsaDomainRefresh.java @@ -29,7 +29,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import org.joda.time.DateTime; +import java.time.Instant; /** * Records of completed and ongoing refresh actions, which recomputes the set of unblockable domains @@ -46,10 +46,10 @@ class BsaDomainRefresh { Long jobId; @Column(nullable = false) - CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create((Instant) null); @Column(nullable = false) - UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null); + UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create((Instant) null); @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -61,7 +61,7 @@ class BsaDomainRefresh { return jobId; } - DateTime getCreationTime() { + Instant getCreationTime() { return creationTime.getTimestamp(); } diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java b/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java index f9cfbfe99..bbb9c6f7c 100644 --- a/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java +++ b/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java @@ -17,6 +17,7 @@ package google.registry.bsa.persistence; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static google.registry.bsa.DownloadStage.DONE; import static google.registry.bsa.DownloadStage.DOWNLOAD_BLOCK_LISTS; +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; import com.google.common.base.Joiner; import com.google.common.base.Objects; @@ -37,8 +38,8 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Locale; -import org.joda.time.DateTime; /** Records of ongoing and completed download jobs. */ @Entity @@ -53,10 +54,10 @@ class BsaDownload { Long jobId; @Column(nullable = false) - CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create((Instant) null); @Column(nullable = false) - UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null); + UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create((Instant) null); @Column(nullable = false) String blockListChecksums = ""; @@ -71,7 +72,7 @@ class BsaDownload { return jobId; } - DateTime getCreationTime() { + Instant getCreationTime() { return creationTime.getTimestamp(); } @@ -83,7 +84,7 @@ class BsaDownload { */ String getJobName() { // Return a value based on job start time, which is unique. - return getCreationTime().toString().toLowerCase(Locale.ROOT).replace(":", ""); + return ISO_8601_FORMATTER.format(getCreationTime()).toLowerCase(Locale.ROOT).replace(":", ""); } boolean isDone() { diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java b/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java index b735607dc..6d60e1b0a 100644 --- a/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java +++ b/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java @@ -19,7 +19,7 @@ import google.registry.persistence.VKey; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.joda.time.DateTime; +import java.time.Instant; /** * Specifies a second-level TLD name that should be blocked from registration in all TLDs except by @@ -41,12 +41,12 @@ final class BsaLabel { */ @SuppressWarnings("unused") @Column(nullable = false) - DateTime creationTime; + Instant creationTime; // For Hibernate. private BsaLabel() {} - BsaLabel(String label, DateTime creationTime) { + BsaLabel(String label, Instant creationTime) { this.label = label; this.creationTime = creationTime; } diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaUnblockableDomain.java b/core/src/main/java/google/registry/bsa/persistence/BsaUnblockableDomain.java index 08d88e555..da5be479b 100644 --- a/core/src/main/java/google/registry/bsa/persistence/BsaUnblockableDomain.java +++ b/core/src/main/java/google/registry/bsa/persistence/BsaUnblockableDomain.java @@ -31,6 +31,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import java.io.Serializable; +import java.time.Instant; /** A domain matching a BSA label but is in use (registered or reserved), so cannot be blocked. */ @Entity @@ -52,7 +53,7 @@ class BsaUnblockableDomain { */ @SuppressWarnings("unused") @Column(nullable = false) - CreateAutoTimestamp createTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp createTime = CreateAutoTimestamp.create((Instant) null); // For Hibernate BsaUnblockableDomain() {} diff --git a/core/src/main/java/google/registry/bsa/persistence/DomainsRefresher.java b/core/src/main/java/google/registry/bsa/persistence/DomainsRefresher.java index aa684c735..e2224c19b 100644 --- a/core/src/main/java/google/registry/bsa/persistence/DomainsRefresher.java +++ b/core/src/main/java/google/registry/bsa/persistence/DomainsRefresher.java @@ -45,14 +45,13 @@ import google.registry.model.domain.Domain; import google.registry.model.tld.Tld; import google.registry.model.tld.Tld.TldType; import google.registry.util.BatchedStreams; +import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Stream; -import org.joda.time.DateTime; -import org.joda.time.Duration; /** * Rechecks {@link BsaUnblockableDomain the registered/reserved domain names} in the database for @@ -89,14 +88,14 @@ import org.joda.time.Duration; */ public final class DomainsRefresher { - private final DateTime prevRefreshStartTime; + private final Instant prevRefreshStartTime; private final int transactionBatchSize; - private final DateTime now; + private final Instant now; public DomainsRefresher( - DateTime prevRefreshStartTime, - DateTime now, - Duration domainTxnMaxDuration, + Instant prevRefreshStartTime, + Instant now, + java.time.Duration domainTxnMaxDuration, int transactionBatchSize) { this.prevRefreshStartTime = prevRefreshStartTime.minus(domainTxnMaxDuration); this.now = now; @@ -249,7 +248,7 @@ public final class DomainsRefresher { } static ImmutableSet getNewlyCreatedUnblockables( - DateTime prevRefreshStartTime, DateTime now) { + Instant prevRefreshStartTime, Instant now) { ImmutableSet bsaEnabledTlds = getTldEntitiesOfType(TldType.REAL).stream() .filter(tld -> isEnrolledWithBsa(tld, now)) @@ -260,7 +259,7 @@ public final class DomainsRefresher { return getBlockedDomainNames(liveDomains); } - static ImmutableSet getAllReservedUnblockables(DateTime now, int batchSize) { + static ImmutableSet getAllReservedUnblockables(Instant now, int batchSize) { Stream allReserved = getAllReservedNames(now); return BatchedStreams.toBatches(allReserved, batchSize) .map(DomainsRefresher::getBlockedDomainNames) diff --git a/core/src/main/java/google/registry/bsa/persistence/DownloadSchedule.java b/core/src/main/java/google/registry/bsa/persistence/DownloadSchedule.java index f470fbd06..daf1d13c8 100644 --- a/core/src/main/java/google/registry/bsa/persistence/DownloadSchedule.java +++ b/core/src/main/java/google/registry/bsa/persistence/DownloadSchedule.java @@ -24,8 +24,8 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import com.google.common.collect.ImmutableMap; import google.registry.bsa.BlockListType; import google.registry.bsa.DownloadStage; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; /** * Information needed when handling a download from BSA. @@ -36,7 +36,7 @@ import org.joda.time.DateTime; */ public record DownloadSchedule( long jobId, - DateTime jobCreationTime, + Instant jobCreationTime, String jobName, DownloadStage stage, Optional latestCompleted, diff --git a/core/src/main/java/google/registry/bsa/persistence/DownloadScheduler.java b/core/src/main/java/google/registry/bsa/persistence/DownloadScheduler.java index 028242a92..d9f7aa827 100644 --- a/core/src/main/java/google/registry/bsa/persistence/DownloadScheduler.java +++ b/core/src/main/java/google/registry/bsa/persistence/DownloadScheduler.java @@ -115,7 +115,11 @@ public final class DownloadScheduler { } private boolean isTimeAgain(BsaDownload mostRecent, Duration interval) { - return mostRecent.getCreationTime().plus(interval).minus(CRON_JITTER).isBefore(clock.nowUtc()); + return mostRecent + .getCreationTime() + .plus(java.time.Duration.ofMillis(interval.getMillis())) + .minus(java.time.Duration.ofMillis(CRON_JITTER.getMillis())) + .isBefore(clock.now()); } /** diff --git a/core/src/main/java/google/registry/bsa/persistence/Queries.java b/core/src/main/java/google/registry/bsa/persistence/Queries.java index d3e49fc10..e22c13f71 100644 --- a/core/src/main/java/google/registry/bsa/persistence/Queries.java +++ b/core/src/main/java/google/registry/bsa/persistence/Queries.java @@ -19,19 +19,16 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.bsa.BsaStringUtils.DOMAIN_SPLITTER; import static google.registry.bsa.BsaTransactions.bsaQuery; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static org.joda.time.DateTimeZone.UTC; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import google.registry.bsa.api.UnblockableDomain; -import google.registry.model.CreateAutoTimestamp; import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.joda.time.DateTime; /** Helpers for querying BSA JPA entities. */ public final class Queries { @@ -135,15 +132,15 @@ public final class Queries { } static ImmutableSet queryNewlyCreatedDomains( - ImmutableCollection tlds, DateTime minCreationTime, DateTime now) { + ImmutableCollection tlds, Instant minCreationTime, Instant now) { return ImmutableSet.copyOf( tm().getEntityManager() .createQuery( - "SELECT domainName FROM Domain WHERE creationTime >= :minCreationTime " + "SELECT domainName FROM Domain WHERE creationTime.creationTime >= :minCreationTime " + "AND deletionTime > :now " + "AND tld in (:tlds)", String.class) - .setParameter("minCreationTime", CreateAutoTimestamp.create(minCreationTime)) + .setParameter("minCreationTime", minCreationTime) .setParameter("now", now) .setParameter("tlds", tlds) .getResultList()); @@ -156,44 +153,36 @@ public final class Queries { * @return The missing unblockables and their creation and deletion time. */ public static ImmutableList queryMissedRegisteredUnblockables( - String tld, DateTime now) { + String tld, Instant now) { String sqlTemplate = """ - SELECT l.domain_name, creation_time, deletion_time - FROM - (SELECT d.domain_name, d.creation_time, d.deletion_time - FROM - "Domain" d - JOIN - (SELECT concat(label, '.', :tld) AS domain_name from "BsaLabel") b - ON b.domain_name = d.domain_name - WHERE deletion_time > :now) l - LEFT OUTER JOIN - (SELECT concat(label, '.', tld) as domain_name - FROM "BsaUnblockableDomain" - WHERE tld = :tld and reason = 'REGISTERED') r - ON l.domain_name = r.domain_name - WHERE r.domain_name is null; - """; + SELECT l.domain_name, creation_time, deletion_time + FROM + (SELECT d.domain_name, d.creation_time, d.deletion_time + FROM + "Domain" d + JOIN + (SELECT concat(label, '.', :tld) AS domain_name from "BsaLabel") b + ON b.domain_name = d.domain_name + WHERE deletion_time > :now) l + LEFT OUTER JOIN + (SELECT concat(label, '.', tld) as domain_name + FROM "BsaUnblockableDomain" + WHERE tld = :tld and reason = 'REGISTERED') r + ON l.domain_name = r.domain_name + WHERE r.domain_name is null; + """; return ((Stream) tm().getEntityManager() .createNativeQuery(sqlTemplate) .setParameter("tld", tld) - .setParameter("now", Instant.ofEpochMilli(now.getMillis())) + .setParameter("now", now) .getResultStream()) .map(Object[].class::cast) - .map( - row -> - new DomainLifeSpan( - (String) row[0], toDateTime((Instant) row[1]), toDateTime((Instant) row[2]))) + .map(row -> new DomainLifeSpan((String) row[0], (Instant) row[1], (Instant) row[2])) .collect(toImmutableList()); } - // For testing convenience: 'assertEquals' fails between `new DateTime(timestamp)` and below. - static DateTime toDateTime(Instant timestamp) { - return new DateTime(timestamp.toEpochMilli(), UTC); - } - - public record DomainLifeSpan(String domainName, DateTime creationTime, DateTime deletionTime) {} + public record DomainLifeSpan(String domainName, Instant creationTime, Instant deletionTime) {} } diff --git a/core/src/main/java/google/registry/bsa/persistence/RefreshSchedule.java b/core/src/main/java/google/registry/bsa/persistence/RefreshSchedule.java index 22c838e7c..8bdad21d2 100644 --- a/core/src/main/java/google/registry/bsa/persistence/RefreshSchedule.java +++ b/core/src/main/java/google/registry/bsa/persistence/RefreshSchedule.java @@ -19,7 +19,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import com.google.errorprone.annotations.CanIgnoreReturnValue; import google.registry.bsa.RefreshStage; -import org.joda.time.DateTime; +import java.time.Instant; /** * Information needed when handling a domain refresh. @@ -28,10 +28,10 @@ import org.joda.time.DateTime; */ public record RefreshSchedule( long jobId, - DateTime jobCreationTime, + Instant jobCreationTime, String jobName, RefreshStage stage, - DateTime prevRefreshTime) { + Instant prevRefreshTime) { /** Updates the current job to the new stage. */ @CanIgnoreReturnValue @@ -50,7 +50,7 @@ public record RefreshSchedule( }); } - static RefreshSchedule create(BsaDomainRefresh job, DateTime prevJobCreationTime) { + static RefreshSchedule create(BsaDomainRefresh job, Instant prevJobCreationTime) { return new RefreshSchedule( job.getJobId(), job.getCreationTime(), diff --git a/core/src/main/java/google/registry/bsa/persistence/RefreshScheduler.java b/core/src/main/java/google/registry/bsa/persistence/RefreshScheduler.java index 40676effc..1efd1d25a 100644 --- a/core/src/main/java/google/registry/bsa/persistence/RefreshScheduler.java +++ b/core/src/main/java/google/registry/bsa/persistence/RefreshScheduler.java @@ -20,8 +20,8 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import jakarta.inject.Inject; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; /** Assigns work for each cron invocation of domain refresh job. */ public class RefreshScheduler { @@ -56,7 +56,7 @@ public class RefreshScheduler { return Optional.empty(); } - DateTime prevDownloadTime = mostRecentDownload.get().getCreationTime(); + Instant prevDownloadTime = mostRecentDownload.get().getCreationTime(); if (recentJobs.isEmpty()) { return Optional.of(scheduleNewJob(prevDownloadTime)); } else { @@ -65,13 +65,13 @@ public class RefreshScheduler { }); } - RefreshSchedule scheduleNewJob(DateTime prevRefreshTime) { + RefreshSchedule scheduleNewJob(Instant prevRefreshTime) { BsaDomainRefresh newJob = new BsaDomainRefresh(); tm().insert(newJob); return RefreshSchedule.create(newJob, prevRefreshTime); } - RefreshSchedule rescheduleOngoingJob(BsaDomainRefresh ongoingJob, DateTime prevJobStartTime) { + RefreshSchedule rescheduleOngoingJob(BsaDomainRefresh ongoingJob, Instant prevJobStartTime) { return RefreshSchedule.create(ongoingJob, prevJobStartTime); } diff --git a/core/src/main/java/google/registry/export/ExportDomainListsAction.java b/core/src/main/java/google/registry/export/ExportDomainListsAction.java index fd5bb265e..69a742f7f 100644 --- a/core/src/main/java/google/registry/export/ExportDomainListsAction.java +++ b/core/src/main/java/google/registry/export/ExportDomainListsAction.java @@ -114,13 +114,13 @@ public class ExportDomainListsAction implements Runnable { .unwrap(NativeQuery.class) .setTupleTransformer(new DomainResultTransformer()) .setParameter("tld", tld) - .setParameter("now", replicaTm().getTransactionTime().toString()) + .setParameter("now", replicaTm().getTxTime().toString()) .getResultList(); } else { return replicaTm() .query(SELECT_DOMAINS_STATEMENT, String.class) .setParameter("tld", tld) - .setParameter("now", replicaTm().getTransactionTime()) + .setParameter("now", replicaTm().getTxTime()) .getResultList(); } }); diff --git a/core/src/main/java/google/registry/flows/certs/CertificateChecker.java b/core/src/main/java/google/registry/flows/certs/CertificateChecker.java index e18283396..c9ad04001 100644 --- a/core/src/main/java/google/registry/flows/certs/CertificateChecker.java +++ b/core/src/main/java/google/registry/flows/certs/CertificateChecker.java @@ -16,6 +16,7 @@ package google.registry.flows.certs; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.toDateTime; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableSet; @@ -94,8 +95,8 @@ public class CertificateChecker { } private static int getValidityLengthInDays(X509Certificate certificate) { - DateTime start = DateTime.parse(certificate.getNotBefore().toInstant().toString()); - DateTime end = DateTime.parse(certificate.getNotAfter().toInstant().toString()); + DateTime start = toDateTime(certificate.getNotBefore().toInstant()); + DateTime end = toDateTime(certificate.getNotAfter().toInstant()); return Days.daysBetween(start.withTimeAtStartOfDay(), end.withTimeAtStartOfDay()).getDays(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 39fca0865..ed5776b7c 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -51,8 +51,8 @@ import static google.registry.model.tld.Tld.TldState.QUIET_PERIOD; import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE; import static google.registry.model.tld.label.ReservationType.NAME_COLLISION; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.leapSafeAddYears; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.plusYears; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -331,7 +331,7 @@ public final class DomainCreateFlow implements MutatingFlow { validateFeeChallenge(feeCreate, feesAndCredits, defaultTokenUsed); Optional secDnsCreate = validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); - DateTime registrationExpirationTime = leapSafeAddYears(now, years); + DateTime registrationExpirationTime = plusYears(now, years); String repoId = createDomainRepoId(tm().allocateId(), tld.getTldStr()); long historyRevisionId = tm().allocateId(); HistoryEntryId domainHistoryId = new HistoryEntryId(repoId, historyRevisionId); @@ -630,7 +630,7 @@ public final class DomainCreateFlow implements MutatingFlow { .setTargetId(targetId) .setRegistrarId(registrarId) .setEventTime(registrationExpirationTime) - .setRecurrenceEndTime(END_OF_TIME) + .setRecurrenceEndTime(END_INSTANT) .setDomainHistoryId(domainHistoryId) .setRenewalPriceBehavior(renewalPriceBehavior) .setRenewalPrice(allocationToken.flatMap(AllocationToken::getRenewalPrice).orElse(null)) diff --git a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java index 9cb5a7c4b..6cac24304 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java @@ -40,6 +40,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.CollectionUtils.union; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -201,7 +202,7 @@ public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLogging GracePeriod.createWithoutBillingEvent( GracePeriodStatus.REDEMPTION, existingDomain.getRepoId(), - redemptionTime, + toInstant(redemptionTime), registrarId))); // Note: The expiration time is unchanged, so if it's before the new deletion time, there will // be a "phantom autorenew" where the expiration time advances. No poll message will be diff --git a/core/src/main/java/google/registry/flows/domain/DomainDeletionTimeCache.java b/core/src/main/java/google/registry/flows/domain/DomainDeletionTimeCache.java index 3c7afd2df..361e2dc7f 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainDeletionTimeCache.java +++ b/core/src/main/java/google/registry/flows/domain/DomainDeletionTimeCache.java @@ -104,7 +104,7 @@ public class DomainDeletionTimeCache { ForeignKeyUtils.loadMostRecentResources( Domain.class, ImmutableSet.of(domainName), false) .get(domainName); - return mostRecentResource == null ? null : mostRecentResource.deletionTime(); + return mostRecentResource == null ? null : mostRecentResource.getDeletionTime(); }; // Unfortunately, maintenance tasks aren't necessarily already in a transaction diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index 3a2fdd196..458535425 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -42,7 +42,8 @@ import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.isAtOrAfter; -import static google.registry.util.DateTimeUtils.leapSafeAddYears; +import static google.registry.util.DateTimeUtils.plusYears; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.DomainNameUtils.ACE_PREFIX; import static java.util.stream.Collectors.joining; @@ -565,7 +566,7 @@ public class DomainFlowUtils { } BillingRecurrence newBillingRecurrence = - existingBillingRecurrence.asBuilder().setRecurrenceEndTime(newEndTime).build(); + existingBillingRecurrence.asBuilder().setRecurrenceEndTime(toInstant(newEndTime)).build(); tm().update(newBillingRecurrence); return newBillingRecurrence; } @@ -847,7 +848,7 @@ public class DomainFlowUtils { */ public static void validateRegistrationPeriod(DateTime now, DateTime newExpirationTime) throws EppException { - if (leapSafeAddYears(now, MAX_REGISTRATION_YEARS).isBefore(newExpirationTime)) { + if (plusYears(now, MAX_REGISTRATION_YEARS).isBefore(newExpirationTime)) { throw new ExceedsMaxRegistrationYearsException(); } } @@ -1113,7 +1114,7 @@ public class DomainFlowUtils { "FROM DomainHistory WHERE modificationTime >= :beginning AND repoId = " + ":repoId ORDER BY modificationTime ASC", DomainHistory.class) - .setParameter("beginning", now.minus(maxSearchPeriod)) + .setParameter("beginning", toInstant(now.minus(maxSearchPeriod))) .setParameter("repoId", domain.getRepoId()) .getResultList(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java index 32278bf53..1c4eef20b 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java @@ -34,7 +34,7 @@ import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeA import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyBulkTokenAllowedOnDomain; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.leapSafeAddYears; +import static google.registry.util.DateTimeUtils.plusYears; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -191,7 +191,7 @@ public final class DomainRenewFlow implements MutatingFlow { existingDomain = maybeApplyBulkPricingRemovalToken(existingDomain, allocationToken); DateTime newExpirationTime = - leapSafeAddYears(existingDomain.getRegistrationExpirationDateTime(), years); // Uncapped + plusYears(existingDomain.getRegistrationExpirationDateTime(), years); // Uncapped validateRegistrationPeriod(now, newExpirationTime); Optional feeRenew = eppInput.getSingleExtension(FeeRenewCommandExtension.class); diff --git a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index 3ec154e7f..44b0de96b 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -30,7 +30,7 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNo import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RESTORE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -163,14 +163,14 @@ public final class DomainRestoreRequestFlow implements MutatingFlow { BillingRecurrence autorenewEvent = newAutorenewBillingEvent(existingDomain) .setEventTime(newExpirationTime) - .setRecurrenceEndTime(END_OF_TIME) + .setRecurrenceEndTime(END_INSTANT) .setDomainHistoryId(domainHistoryId) .build(); entitiesToInsert.add(autorenewEvent); PollMessage.Autorenew autorenewPollMessage = newAutorenewPollMessage(existingDomain) .setEventTime(newExpirationTime) - .setAutorenewEndTime(END_OF_TIME) + .setAutorenewEndTime(END_INSTANT) .setDomainHistoryId(domainHistoryId) .build(); entitiesToInsert.add(autorenewPollMessage); @@ -242,7 +242,7 @@ public final class DomainRestoreRequestFlow implements MutatingFlow { return existingDomain .asBuilder() .setRegistrationExpirationTime(newExpirationTime) - .setDeletionTime(END_OF_TIME) + .setDeletionTime(END_INSTANT) .setStatusValues(null) .setGracePeriods(null) .setDeletePollMessage(null) diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java index 7b7eb47b3..ffabb6d78 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java @@ -32,7 +32,7 @@ import static google.registry.model.reporting.DomainTransactionRecord.Transactio import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.union; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.DateTimeUtils.toInstant; @@ -211,7 +211,7 @@ public final class DomainTransferApproveFlow implements MutatingFlow { ? RenewalPriceBehavior.DEFAULT : existingBillingRecurrence.getRenewalPriceBehavior()) .setRenewalPrice(renewalPrice) - .setRecurrenceEndTime(END_OF_TIME) + .setRecurrenceEndTime(END_INSTANT) .setDomainHistoryId(domainHistoryId) .build(); // Create a new autorenew poll message. @@ -220,7 +220,7 @@ public final class DomainTransferApproveFlow implements MutatingFlow { .setTargetId(targetId) .setRegistrarId(gainingRegistrarId) .setEventTime(newExpirationTime) - .setAutorenewEndTime(END_OF_TIME) + .setAutorenewEndTime(END_INSTANT) .setMsg("Domain was auto-renewed.") .setDomainHistoryId(domainHistoryId) .build(); diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java b/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java index f9c0ab163..84c94cc49 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java @@ -16,7 +16,7 @@ package google.registry.flows.domain; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.MoreCollectors.onlyElement; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -240,7 +240,7 @@ public final class DomainTransferUtils { .setTargetId(targetId) .setRegistrarId(gainingRegistrarId) .setEventTime(serverApproveNewExpirationTime) - .setAutorenewEndTime(END_OF_TIME) + .setAutorenewEndTime(END_INSTANT) .setMsg("Domain was auto-renewed.") .setDomainHistoryId(domainHistoryId) .build(); @@ -258,7 +258,7 @@ public final class DomainTransferUtils { .setTargetId(targetId) .setRegistrarId(gainingRegistrarId) .setEventTime(serverApproveNewExpirationTime) - .setRecurrenceEndTime(END_OF_TIME) + .setRecurrenceEndTime(END_INSTANT) .setRenewalPriceBehavior(existingBillingRecurrence.getRenewalPriceBehavior()) .setRenewalPrice(existingBillingRecurrence.getRenewalPrice().orElse(null)) .setDomainHistoryId(domainHistoryId) diff --git a/core/src/main/java/google/registry/flows/host/HostInfoFlow.java b/core/src/main/java/google/registry/flows/host/HostInfoFlow.java index 4647ade89..a8df0ae70 100644 --- a/core/src/main/java/google/registry/flows/host/HostInfoFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostInfoFlow.java @@ -19,6 +19,7 @@ import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.host.HostFlowUtils.validateHostName; import static google.registry.model.EppResourceUtils.isLinked; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toDateTime; import com.google.common.collect.ImmutableSet; import google.registry.flows.EppException; @@ -80,7 +81,7 @@ public final class HostInfoFlow implements TransactionalFlow { tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now); hostInfoDataBuilder .setCurrentSponsorRegistrarId(superordinateDomain.getCurrentSponsorRegistrarId()) - .setLastTransferTime(host.computeLastTransferTime(superordinateDomain)); + .setLastTransferTime(toDateTime(host.computeLastTransferTime(superordinateDomain))); if (superordinateDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) { statusValues.add(StatusValue.PENDING_TRANSFER); } diff --git a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java index 7d537403e..ce6f0df7b 100644 --- a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java @@ -32,6 +32,7 @@ import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomain import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.isNullOrEmpty; +import static google.registry.util.DateTimeUtils.toDateTime; import com.google.cloud.tasks.v2.Task; import com.google.common.collect.ImmutableMultimap; @@ -175,7 +176,8 @@ public final class HostUpdateFlow implements MutatingFlow { // have just completed. This is only critical for updates that rename a host away from its // current superordinate domain, where we must "freeze" the last transfer time, but it's easiest // to just update it unconditionally. - DateTime lastTransferTime = existingHost.computeLastTransferTime(oldSuperordinateDomain); + DateTime lastTransferTime = + toDateTime(existingHost.computeLastTransferTime(oldSuperordinateDomain)); // Copy the clientId onto the host. This is only really needed when the host will be external, // since external hosts store their own clientId. For subordinate hosts the canonical clientId // comes from the superordinate domain, but we might as well update the persisted value. For diff --git a/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java b/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java index 8b5893913..7f36d88b7 100644 --- a/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java +++ b/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java @@ -19,22 +19,47 @@ import static google.registry.persistence.transaction.QueryComposer.Comparator.E import static google.registry.persistence.transaction.QueryComposer.Comparator.LTE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.isBeforeOrAt; +import static google.registry.util.DateTimeUtils.plusYears; +import static google.registry.util.DateTimeUtils.toInstant; import google.registry.model.poll.PollMessage; import google.registry.persistence.transaction.QueryComposer; +import java.time.Instant; import java.util.Optional; import org.joda.time.DateTime; /** Static utility functions for poll flows. */ public final class PollFlowUtils { - /** Returns the number of poll messages for the given registrar that are not in the future. */ + /** + * Returns the number of poll messages for the given registrar that are not in the future. + * + * @deprecated Use {@link #getPollMessageCount(String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static int getPollMessageCount(String registrarId, DateTime now) { + return getPollMessageCount(registrarId, toInstant(now)); + } + + /** Returns the number of poll messages for the given registrar that are not in the future. */ + public static int getPollMessageCount(String registrarId, Instant now) { return (int) createPollMessageQuery(registrarId, now).count(); } - /** Returns the first (by event time) poll message not in the future for this registrar. */ + /** + * Returns the first (by event time) poll message not in the future for this registrar. + * + * @deprecated Use {@link #getFirstPollMessage(String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static Optional getFirstPollMessage(String registrarId, DateTime now) { + return getFirstPollMessage(registrarId, toInstant(now)); + } + + /** Returns the first (by event time) poll message not in the future for this registrar. */ + public static Optional getFirstPollMessage(String registrarId, Instant now) { return createPollMessageQuery(registrarId, now).orderBy("eventTime").first(); } @@ -47,22 +72,22 @@ public final class PollFlowUtils { */ public static void ackPollMessage(PollMessage pollMessage) { checkArgument( - isBeforeOrAt(pollMessage.getEventTime(), tm().getTransactionTime()), + isBeforeOrAt(pollMessage.getEventTimeInstant(), tm().getTxTime()), "Cannot ACK poll message with ID %s because its event time is in the future: %s", pollMessage.getId(), - pollMessage.getEventTime()); + pollMessage.getEventTimeInstant()); if (pollMessage instanceof PollMessage.OneTime) { // One-time poll messages are deleted once acked. tm().delete(pollMessage.createVKey()); } else if (pollMessage instanceof PollMessage.Autorenew autorenewPollMessage) { // Move the eventTime of this autorenew poll message forward by a year. - DateTime nextEventTime = autorenewPollMessage.getEventTime().plusYears(1); + Instant nextEventTime = plusYears(autorenewPollMessage.getEventTimeInstant(), 1); // If the next event falls within the bounds of the end time, then just update the eventTime // and re-save it for future autorenew poll messages to be delivered. Otherwise, this // autorenew poll message has no more events to deliver and should be deleted. - if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTime())) { + if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTimeInstant())) { tm().put(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build()); } else { tm().delete(autorenewPollMessage.createVKey()); @@ -75,9 +100,21 @@ public final class PollFlowUtils { /** * Returns the QueryComposer for poll messages from the given registrar that are not in the * future. + * + * @deprecated Use {@link #createPollMessageQuery(String, Instant)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static QueryComposer createPollMessageQuery( String registrarId, DateTime now) { + return createPollMessageQuery(registrarId, toInstant(now)); + } + + /** + * Returns the QueryComposer for poll messages from the given registrar that are not in the + * future. + */ + public static QueryComposer createPollMessageQuery(String registrarId, Instant now) { return tm().createQueryComposer(PollMessage.class) .where("clientId", EQ, registrarId) .where("eventTime", LTE, now); diff --git a/core/src/main/java/google/registry/flows/poll/PollRequestFlow.java b/core/src/main/java/google/registry/flows/poll/PollRequestFlow.java index 04b00ed93..d24e36370 100644 --- a/core/src/main/java/google/registry/flows/poll/PollRequestFlow.java +++ b/core/src/main/java/google/registry/flows/poll/PollRequestFlow.java @@ -35,8 +35,8 @@ import google.registry.model.poll.PollMessageExternalKeyConverter; import google.registry.persistence.IsolationLevel; import google.registry.persistence.PersistenceModule.TransactionIsolationLevel; import jakarta.inject.Inject; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; /** * An EPP flow for requesting {@link PollMessage}s. @@ -67,7 +67,7 @@ public final class PollRequestFlow implements TransactionalFlow { } // Return the oldest message from the queue. - DateTime now = tm().getTransactionTime(); + Instant now = tm().getTxTime(); Optional maybePollMessage = getFirstPollMessage(registrarId, now); if (maybePollMessage.isEmpty()) { return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build(); diff --git a/core/src/main/java/google/registry/model/CreateAutoTimestamp.java b/core/src/main/java/google/registry/model/CreateAutoTimestamp.java index cf9be0b7d..0477d2bfa 100644 --- a/core/src/main/java/google/registry/model/CreateAutoTimestamp.java +++ b/core/src/main/java/google/registry/model/CreateAutoTimestamp.java @@ -10,17 +10,20 @@ // 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 Licenseschema.. +// limitations under the License. package google.registry.model; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.gson.annotations.Expose; import google.registry.persistence.EntityCallbacksListener.RecursivePrePersist; import google.registry.persistence.EntityCallbacksListener.RecursivePreUpdate; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import java.time.Instant; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -30,25 +33,44 @@ public class CreateAutoTimestamp extends ImmutableObject implements UnsafeSerial @Column(nullable = false) @Expose - DateTime creationTime; + Instant creationTime; @RecursivePrePersist @RecursivePreUpdate public void setTimestamp() { if (creationTime == null) { - creationTime = tm().getTransactionTime(); + creationTime = tm().getTxTime(); } } /** Returns the timestamp. */ @Nullable - public DateTime getTimestamp() { + public Instant getTimestamp() { return creationTime; } - public static CreateAutoTimestamp create(@Nullable DateTime creationTime) { + /** + * @deprecated Use {@link #getTimestamp()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + @Nullable + public DateTime getTimestampDateTime() { + return toDateTime(creationTime); + } + + public static CreateAutoTimestamp create(@Nullable Instant creationTime) { CreateAutoTimestamp instance = new CreateAutoTimestamp(); instance.creationTime = creationTime; return instance; } + + /** + * @deprecated Use {@link #create(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static CreateAutoTimestamp create(@Nullable DateTime creationTime) { + return create(toInstant(creationTime)); + } } diff --git a/core/src/main/java/google/registry/model/EntityYamlUtils.java b/core/src/main/java/google/registry/model/EntityYamlUtils.java index 32e59a75a..7f59e4794 100644 --- a/core/src/main/java/google/registry/model/EntityYamlUtils.java +++ b/core/src/main/java/google/registry/model/EntityYamlUtils.java @@ -16,6 +16,8 @@ package google.registry.model; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; import static com.google.common.collect.Ordering.natural; +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; +import static google.registry.util.DateTimeUtils.parseInstant; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -38,6 +40,7 @@ import google.registry.model.tld.Tld.TldState; import google.registry.persistence.VKey; import java.io.IOException; import java.math.BigDecimal; +import java.time.Instant; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -46,7 +49,6 @@ import java.util.Set; import java.util.SortedMap; import org.joda.money.CurrencyUnit; import org.joda.money.Money; -import org.joda.time.DateTime; import org.joda.time.Duration; /** A collection of static utility classes/functions to convert entities to/from YAML files. */ @@ -59,8 +61,11 @@ public class EntityYamlUtils { SimpleModule module = new SimpleModule(); module.addSerializer(Money.class, new MoneySerializer()); module.addDeserializer(Money.class, new MoneyDeserializer()); + module.addSerializer(CreateAutoTimestamp.class, new CreateAutoTimestampSerializer()); + module.addDeserializer(CreateAutoTimestamp.class, new CreateAutoTimestampDeserializer()); module.addSerializer(Duration.class, new DurationSerializer()); - module.addSerializer(TimedTransitionProperty.class, new TimedTransitionPropertySerializer()); + module.addSerializer(Instant.class, new InstantSerializer()); + module.addDeserializer(Instant.class, new InstantDeserializer()); ObjectMapper mapper = JsonMapper.builder(new YAMLFactory().disable(Feature.WRITE_DOC_START_MARKER)) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) @@ -312,24 +317,6 @@ public class EntityYamlUtils { } } - /** A custom JSON serializer for a {@link TimedTransitionProperty} of {@link Enum} values. */ - public static class TimedTransitionPropertySerializer> - extends StdSerializer> { - - TimedTransitionPropertySerializer() { - super(null, true); - } - - @Override - public void serialize( - TimedTransitionProperty data, - JsonGenerator jsonGenerator, - SerializerProvider serializerProvider) - throws IOException { - jsonGenerator.writeObject(data.toValueMap()); - } - } - /** A custom JSON deserializer for a {@link TimedTransitionProperty} of {@link TldState}. */ public static class TimedTransitionPropertyTldStateDeserializer extends StdDeserializer> { @@ -346,11 +333,13 @@ public class EntityYamlUtils { public TimedTransitionProperty deserialize( JsonParser jp, DeserializationContext context) throws IOException { SortedMap valueMap = jp.readValueAs(SortedMap.class); - return TimedTransitionProperty.fromValueMap( + return TimedTransitionProperty.fromValueMapInstant( valueMap.keySet().stream() .collect( toImmutableSortedMap( - natural(), DateTime::parse, key -> TldState.valueOf(valueMap.get(key))))); + natural(), + key -> parseInstant(key), + key -> TldState.valueOf(valueMap.get(key))))); } } @@ -370,12 +359,12 @@ public class EntityYamlUtils { public TimedTransitionProperty deserialize(JsonParser jp, DeserializationContext context) throws IOException { SortedMap> valueMap = jp.readValueAs(SortedMap.class); - return TimedTransitionProperty.fromValueMap( + return TimedTransitionProperty.fromValueMapInstant( valueMap.keySet().stream() .collect( toImmutableSortedMap( natural(), - DateTime::parse, + key -> parseInstant(key), key -> Money.of( CurrencyUnit.of(valueMap.get(key).get("currency").toString()), @@ -400,16 +389,38 @@ public class EntityYamlUtils { public TimedTransitionProperty deserialize( JsonParser jp, DeserializationContext context) throws IOException { SortedMap valueMap = jp.readValueAs(SortedMap.class); - return TimedTransitionProperty.fromValueMap( + return TimedTransitionProperty.fromValueMapInstant( valueMap.keySet().stream() .collect( toImmutableSortedMap( natural(), - DateTime::parse, + key -> parseInstant(key), key -> FeatureStatus.valueOf(valueMap.get(key))))); } } + /** A custom JSON serializer for a {@link CreateAutoTimestamp}. */ + public static class CreateAutoTimestampSerializer extends StdSerializer { + + public CreateAutoTimestampSerializer() { + this(null); + } + + public CreateAutoTimestampSerializer(Class t) { + super(t); + } + + @Override + public void serialize(CreateAutoTimestamp value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + if (value.getTimestamp() == null) { + gen.writeNull(); + } else { + gen.writeString(ISO_8601_FORMATTER.format(value.getTimestamp())); + } + } + } + /** A custom JSON deserializer for a {@link CreateAutoTimestamp}. */ public static class CreateAutoTimestampDeserializer extends StdDeserializer { @@ -424,8 +435,34 @@ public class EntityYamlUtils { @Override public CreateAutoTimestamp deserialize(JsonParser jp, DeserializationContext context) throws IOException { - DateTime creationTime = jp.readValueAs(DateTime.class); - return CreateAutoTimestamp.create(creationTime); + String creationTime = jp.getText(); + return CreateAutoTimestamp.create(parseInstant(creationTime)); + } + } + + /** A custom JSON serializer for {@link Instant}. */ + public static class InstantSerializer extends StdSerializer { + + public InstantSerializer() { + super(Instant.class); + } + + @Override + public void serialize(Instant value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeString(ISO_8601_FORMATTER.format(value)); + } + } + + /** A custom JSON deserializer for {@link Instant}. */ + public static class InstantDeserializer extends StdDeserializer { + public InstantDeserializer() { + super(Instant.class); + } + + @Override + public Instant deserialize(JsonParser jp, DeserializationContext context) throws IOException { + return parseInstant(jp.getText()); } } } diff --git a/core/src/main/java/google/registry/model/EppResource.java b/core/src/main/java/google/registry/model/EppResource.java index f8007038f..8667a3b5e 100644 --- a/core/src/main/java/google/registry/model/EppResource.java +++ b/core/src/main/java/google/registry/model/EppResource.java @@ -24,7 +24,8 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.DateTimeUtils.toInstant; import com.github.benmanes.caffeine.cache.CacheLoader; @@ -109,7 +110,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B // Need to override the default non-null column attribute. @AttributeOverride(name = "creationTime", column = @Column) @Expose - CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create((Instant) null); /** * The time when this resource was or will be deleted. @@ -124,7 +125,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B * out of the index at that time, as long as we query for resources whose deletion time is before * now. */ - DateTime deletionTime; + Instant deletionTime; /** * The time that this resource was last updated. @@ -133,7 +134,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B * edits; it only includes EPP-visible modifications such as {@literal }. Can be null if * the resource has never been modified. */ - @Expose DateTime lastEppUpdateTime; + @Expose Instant lastEppUpdateTime; /** Status values associated with this resource. */ @Enumerated(EnumType.STRING) @@ -157,7 +158,16 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B this.repoId = repoId; } + /** + * @deprecated Use {@link #getCreationTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public final DateTime getCreationTime() { + return creationTime.getTimestampDateTime(); + } + + public final Instant getCreationTimeInstant() { return creationTime.getTimestamp(); } @@ -166,12 +176,13 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B } @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastEppUpdateDateTime() { - return lastEppUpdateTime; + return toDateTime(lastEppUpdateTime); } public Instant getLastEppUpdateTime() { - return toInstant(lastEppUpdateTime); + return lastEppUpdateTime; } public String getLastEppUpdateRegistrarId() { @@ -193,12 +204,13 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B } @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getDeletionDateTime() { - return deletionTime; + return toDateTime(deletionTime); } public Instant getDeletionTime() { - return toInstant(deletionTime); + return deletionTime; } /** Return a clone of the resource with timed status values modified using the given time. */ @@ -240,7 +252,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B *

Note: This can only be used if the creation time hasn't already been set, which it is in * normal EPP flows. */ - public B setCreationTime(DateTime creationTime) { + public B setCreationTime(Instant creationTime) { checkState( getInstance().creationTime.getTimestamp() == null, "creationTime can only be set once for EppResource."); @@ -248,19 +260,47 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B return thisCastToDerived(); } + /** + * @deprecated Use {@link #setCreationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setCreationTime(DateTime creationTime) { + return setCreationTime(toInstant(creationTime)); + } + /** Set the time this resource was created. Should only be used in tests. */ @VisibleForTesting - public B setCreationTimeForTest(DateTime creationTime) { + public B setCreationTimeForTest(Instant creationTime) { getInstance().creationTime = CreateAutoTimestamp.create(creationTime); return thisCastToDerived(); } + /** + * @deprecated Use {@link #setCreationTimeForTest(Instant)} + */ + @Deprecated + @VisibleForTesting + @SuppressWarnings("InlineMeSuggester") + public B setCreationTimeForTest(DateTime creationTime) { + return setCreationTimeForTest(toInstant(creationTime)); + } + /** Set the time after which this resource should be considered deleted. */ - public B setDeletionTime(DateTime deletionTime) { + public B setDeletionTime(Instant deletionTime) { getInstance().deletionTime = deletionTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setDeletionTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setDeletionTime(DateTime deletionTime) { + return setDeletionTime(toInstant(deletionTime)); + } + /** Set the current sponsoring registrar. */ public B setPersistedCurrentSponsorRegistrarId(String currentSponsorRegistrarId) { getInstance().currentSponsorRegistrarId = currentSponsorRegistrarId; @@ -274,11 +314,20 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B } /** Set the time when a {@literal } was performed on this resource. */ - public B setLastEppUpdateTime(DateTime lastEppUpdateTime) { + public B setLastEppUpdateTime(Instant lastEppUpdateTime) { getInstance().lastEppUpdateTime = lastEppUpdateTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setLastEppUpdateTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setLastEppUpdateTime(DateTime lastEppUpdateTime) { + return setLastEppUpdateTime(toInstant(lastEppUpdateTime)); + } + /** Set the registrar who last performed a {@literal } on this resource. */ public B setLastEppUpdateRegistrarId(String lastEppUpdateRegistrarId) { getInstance().lastEppUpdateRegistrarId = lastEppUpdateRegistrarId; @@ -345,8 +394,8 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B if (getInstance().getStatusValues().isEmpty()) { addStatusValue(StatusValue.OK); } - // If there is no deletion time, set it to END_OF_TIME. - setDeletionTime(Optional.ofNullable(getInstance().deletionTime).orElse(END_OF_TIME)); + // If there is no deletion time, set it to END_INSTANT. + setDeletionTime(Optional.ofNullable(getInstance().deletionTime).orElse(END_INSTANT)); return ImmutableObject.cloneEmptyToNull(super.build()); } } diff --git a/core/src/main/java/google/registry/model/EppResourceUtils.java b/core/src/main/java/google/registry/model/EppResourceUtils.java index c64591ce4..94b892c3c 100644 --- a/core/src/main/java/google/registry/model/EppResourceUtils.java +++ b/core/src/main/java/google/registry/model/EppResourceUtils.java @@ -17,9 +17,10 @@ package google.registry.model; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; import static google.registry.util.DateTimeUtils.isAtOrAfter; import static google.registry.util.DateTimeUtils.isBeforeOrAt; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableSet; import com.google.common.flogger.FluentLogger; @@ -39,7 +40,6 @@ import java.util.Comparator; import java.util.function.Function; import javax.annotation.Nullable; import org.joda.time.DateTime; -import org.joda.time.Interval; /** Utilities for working with {@link EppResource}. */ public final class EppResourceUtils { @@ -79,22 +79,33 @@ public final class EppResourceUtils { return (T resource) -> cloneProjectedAtTime(resource, now); } + public static boolean isActive(EppResource resource, Instant time) { + return isAtOrAfter(time, resource.getCreationTimeInstant()) + && time.isBefore(resource.getDeletionTime()); + } + /** - * The lifetime of a resource is from its creation time, inclusive, through its deletion time, - * exclusive, which happily maps to the behavior of Interval. + * @deprecated Use {@link #isActive(EppResource, Instant)} */ - private static Interval getLifetime(EppResource resource) { - return new Interval(resource.getCreationTime(), resource.getDeletionDateTime()); - } - + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static boolean isActive(EppResource resource, DateTime time) { - return getLifetime(resource).contains(time); + return isActive(resource, toInstant(time)); } - public static boolean isDeleted(EppResource resource, DateTime time) { + public static boolean isDeleted(EppResource resource, Instant time) { return !isActive(resource, time); } + /** + * @deprecated Use {@link #isDeleted(EppResource, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static boolean isDeleted(EppResource resource, DateTime time) { + return isDeleted(resource, toInstant(time)); + } + /** Process an automatic transfer on a domain. */ public static void setAutomaticTransferSuccessProperties( DomainBase.Builder builder, DomainTransferData transferData) { @@ -132,7 +143,7 @@ public final class EppResourceUtils { } /** - * Rewinds an {@link EppResource} object to a given point in time. + * Returns the given resource as it was at a specific point in time. * *

This method costs nothing if {@code resource} is already current. Otherwise, it needs to * perform a single fetch operation. @@ -141,12 +152,12 @@ public final class EppResourceUtils { * {@code resource} should be whatever's currently in SQL. * * @return the resource at {@code timestamp} or {@code null} if resource is deleted or not yet - * created + * created. */ public static T loadAtPointInTime( - final T resource, final DateTime timestamp) { + final T resource, final Instant timestamp) { // If we're before the resource creation time, don't try to find a "most recent revision". - if (timestamp.isBefore(resource.getCreationTime())) { + if (timestamp.isBefore(resource.getCreationTimeInstant())) { return null; } // If the resource was not modified after the requested time, then use it as-is, otherwise find @@ -159,23 +170,31 @@ public final class EppResourceUtils { return (loadedResource == null) ? null : (isActive(loadedResource, timestamp) - ? cloneProjectedAtTime(loadedResource, timestamp) + ? (T) loadedResource.cloneProjectedAtInstant(timestamp) : null); } + /** + * @deprecated Use {@link #loadAtPointInTime(EppResource, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static T loadAtPointInTime( + final T resource, final DateTime timestamp) { + return loadAtPointInTime(resource, toInstant(timestamp)); + } + /** * Returns the most recent revision of a given EppResource before or at the provided timestamp, * falling back to using the resource as-is if there are no revisions. - * - * @see #loadAtPointInTime(EppResource, DateTime) */ private static T loadMostRecentRevisionAtTime( - final T resource, final DateTime timestamp) { + final T resource, final Instant timestamp) { @SuppressWarnings("unchecked") T resourceAtPointInTime = (T) HistoryEntryDao.loadHistoryObjectsForResource( - resource.createVKey(), START_OF_TIME, timestamp) + resource.createVKey(), START_INSTANT, timestamp) .stream() .max(Comparator.comparing(HistoryEntry::getModificationTime)) .flatMap(HistoryEntry::getResourceAtPointInTime) @@ -193,18 +212,27 @@ public final class EppResourceUtils { * Returns a set of {@link VKey} for domains that reference a specified host. * * @param key the referent key - * @param now the logical time of the check - * @param limit the maximum number of returned keys, unlimited if null + * @param now the logical time of the check /** Returns the domains that are linked to this host + * at the given time. + * @deprecated Use {@link #getLinkedDomainKeys(VKey, Instant, Integer)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static ImmutableSet> getLinkedDomainKeys( VKey key, DateTime now, @Nullable Integer limit) { + return getLinkedDomainKeys(key, toInstant(now), limit); + } + + /** Returns the domains that are linked to this host at the given time. */ + public static ImmutableSet> getLinkedDomainKeys( + VKey key, Instant now, @Nullable Integer limit) { return tm().reTransact( () -> { Query query = tm().getEntityManager() .createNativeQuery(HOST_LINKED_DOMAIN_QUERY) .setParameter("fkRepoId", key.getKey()) - .setParameter("now", now.toDate()); + .setParameter("now", now); if (limit != null) { query.setMaxResults(limit); } @@ -220,12 +248,18 @@ public final class EppResourceUtils { } /** - * Returns whether the given host is linked to (that is, referenced by) a domain. + * Returns whether this host is linked to any domains at the given time. * - * @param key the referent key - * @param now the logical time of the check + * @deprecated Use {@link #isLinked(VKey, Instant)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static boolean isLinked(VKey key, DateTime now) { + return isLinked(key, toInstant(now)); + } + + /** Returns whether this resource is linked to any domains at the given time. */ + public static boolean isLinked(VKey key, Instant now) { return !getLinkedDomainKeys(key, now, 1).isEmpty(); } diff --git a/core/src/main/java/google/registry/model/ForeignKeyUtils.java b/core/src/main/java/google/registry/model/ForeignKeyUtils.java index ba1ab670b..bc1760c32 100644 --- a/core/src/main/java/google/registry/model/ForeignKeyUtils.java +++ b/core/src/main/java/google/registry/model/ForeignKeyUtils.java @@ -20,6 +20,8 @@ import static google.registry.config.RegistryConfig.getEppResourceCachingDuratio import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.LoadingCache; @@ -63,7 +65,30 @@ public final class ForeignKeyUtils { Domain.class, "domainName", Host.class, "hostName"); - public record MostRecentResource(String repoId, DateTime deletionTime) {} + public record MostRecentResource(String repoId, Instant deletionTime) { + /** + * @deprecated Use {@link #deletionTime()} + */ + @Deprecated + public DateTime getDeletionTime() { + return toDateTime(deletionTime); + } + } + + /** + * Loads an optional {@link VKey} to an {@link EppResource} from the database by foreign key. + * + *

Returns empty if no resource with this foreign key was ever created, or if the most recently + * created resource was deleted before time "now". + * + * @deprecated Use {@link #loadKey(Class, String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static Optional> loadKey( + Class clazz, String foreignKey, DateTime now) { + return loadKey(clazz, foreignKey, toInstant(now)); + } /** * Loads an optional {@link VKey} to an {@link EppResource} from the database by foreign key. @@ -72,7 +97,7 @@ public final class ForeignKeyUtils { * created resource was deleted before time "now". */ public static Optional> loadKey( - Class clazz, String foreignKey, DateTime now) { + Class clazz, String foreignKey, Instant now) { return Optional.ofNullable(loadKeys(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)); } @@ -81,13 +106,14 @@ public final class ForeignKeyUtils { * *

Returns null if no resource with this foreign key was ever created or if the most recently * created resource was deleted before time "now". + * + * @deprecated Use {@link #loadResource(Class, String, Instant)} */ @Deprecated + @SuppressWarnings("InlineMeSuggester") public static Optional loadResource( Class clazz, String foreignKey, DateTime now) { - // Note: no need to project to "now" because loadResources already does - return Optional.ofNullable( - loadResources(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)); + return loadResource(clazz, foreignKey, toInstant(now)); } /** @@ -109,9 +135,25 @@ public final class ForeignKeyUtils { * *

The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist * or has been soft-deleted. + * + * @deprecated Use {@link #loadKeys(Class, Collection, Instant)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static ImmutableMap> loadKeys( Class clazz, Collection foreignKeys, DateTime now) { + return loadKeys(clazz, foreignKeys, toInstant(now)); + } + + /** + * Load a map of {@link String} foreign keys to {@link VKey}s to {@link EppResource} that are + * active at or after the specified moment in time. + * + *

The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist + * or has been soft-deleted. + */ + public static ImmutableMap> loadKeys( + Class clazz, Collection foreignKeys, Instant now) { return loadMostRecentResources(clazz, foreignKeys, false).entrySet().stream() .filter(e -> now.isBefore(e.getValue().deletionTime())) .collect(toImmutableMap(Entry::getKey, e -> VKey.create(clazz, e.getValue().repoId()))); @@ -124,13 +166,11 @@ public final class ForeignKeyUtils { *

The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist * or has been soft-deleted. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "InlineMeSuggester"}) @Deprecated public static ImmutableMap loadResources( Class clazz, Collection foreignKeys, DateTime now) { - return loadMostRecentResourceObjects(clazz, foreignKeys, false).entrySet().stream() - .filter(e -> now.isBefore(e.getValue().getDeletionDateTime())) - .collect(toImmutableMap(Entry::getKey, e -> (E) e.getValue().cloneProjectedAtTime(now))); + return loadResources(clazz, foreignKeys, toInstant(now)); } /** @@ -181,7 +221,7 @@ public final class ForeignKeyUtils { .collect( toImmutableMap( row -> (String) row[0], - row -> new MostRecentResource((String) row[1], (DateTime) row[2])))); + row -> new MostRecentResource((String) row[1], (Instant) row[2])))); } /** Method to load the most recent {@link EppResource}s for the given foreign keys. */ @@ -291,9 +331,28 @@ public final class ForeignKeyUtils { * *

Don't use the cached version of this method unless you really need it for performance * reasons, and are OK with the trade-offs in loss of transactional consistency. + * + * @deprecated Use {@link #loadKeysByCacheIfEnabled(Class, Collection, Instant)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static ImmutableMap> loadKeysByCacheIfEnabled( Class clazz, Collection foreignKeys, DateTime now) { + return loadKeysByCacheIfEnabled(clazz, foreignKeys, toInstant(now)); + } + + /** + * Load a list of {@link VKey} to {@link EppResource} instances by class and foreign key strings + * that are active at or after the specified moment in time, using the cache if enabled. + * + *

The returned map will omit any keys for which the {@link EppResource} doesn't exist or has + * been soft-deleted. + * + *

Don't use the cached version of this method unless you really need it for performance + * reasons, and are OK with the trade-offs in loss of transactional consistency. + */ + public static ImmutableMap> loadKeysByCacheIfEnabled( + Class clazz, Collection foreignKeys, Instant now) { if (!RegistryConfig.isEppResourceCachingEnabled()) { return loadKeys(clazz, foreignKeys, now); } @@ -308,9 +367,21 @@ public final class ForeignKeyUtils { e -> VKey.create(clazz, e.getValue().get().repoId()))); } - /** Loads an optional {@link VKey} to an {@link EppResource} using the cache. */ + /** + * Loads an optional {@link VKey} to an {@link EppResource} using the cache. + * + * @deprecated Use {@link #loadKeyByCache(Class, String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static Optional> loadKeyByCache( Class clazz, String foreignKey, DateTime now) { + return loadKeyByCache(clazz, foreignKey, toInstant(now)); + } + + /** Loads an optional {@link VKey} to an {@link EppResource} using the cache. */ + public static Optional> loadKeyByCache( + Class clazz, String foreignKey, Instant now) { return foreignKeyToRepoIdCache .get(VKey.create(clazz, foreignKey)) .filter(mrr -> now.isBefore(mrr.deletionTime())) @@ -407,14 +478,57 @@ public final class ForeignKeyUtils { * *

Do not call this cached version for anything that needs transactional consistency. It should * only be used when it's OK if the data is potentially being out of date, e.g. RDAP. + * + * @deprecated Use {@link #loadResourceByCacheIfEnabled(Class, String, Instant)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static Optional loadResourceByCacheIfEnabled( Class clazz, String foreignKey, DateTime now) { + return loadResourceByCacheIfEnabled(clazz, foreignKey, toInstant(now)); + } + + /** + * Loads the last created version of an {@link EppResource} from the database by foreign key, + * using a cache, if caching is enabled in config settings. + * + *

Returns null if no resource with this foreign key was ever created, or if the most recently + * created resource was deleted before time "now". + * + *

Loading an {@link EppResource} by itself is not sufficient to know its current state since + * it may have various expirable conditions and status values that might implicitly change its + * state as time progresses even if it has not been updated in the database. Rather, the resource + * must be combined with a timestamp to view its current state. We use a global last updated + * timestamp to guarantee monotonically increasing write times, and forward our projected time to + * the greater of this timestamp or "now". This guarantees that we're not projecting into the + * past. + * + *

Do not call this cached version for anything that needs transactional consistency. It should + * only be used when it's OK if the data is potentially being out of date, e.g. RDAP. + */ + public static Optional loadResourceByCacheIfEnabled( + Class clazz, String foreignKey, Instant now) { return RegistryConfig.isEppResourceCachingEnabled() ? loadResourceByCache(clazz, foreignKey, now) : loadResource(clazz, foreignKey, now); } + /** + * Loads the last created version of an {@link EppResource} from the replica database by foreign + * key, using a cache. + * + *

This method ignores the config setting for caching, and is reserved for use cases that can + * tolerate slightly stale data. + * + * @deprecated Use {@link #loadResourceByCache(Class, String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static Optional loadResourceByCache( + Class clazz, String foreignKey, DateTime now) { + return loadResourceByCache(clazz, foreignKey, toInstant(now)); + } + /** * Loads the last created version of an {@link EppResource} from the replica database by foreign * key, using a cache. @@ -424,11 +538,11 @@ public final class ForeignKeyUtils { */ @SuppressWarnings("unchecked") public static Optional loadResourceByCache( - Class clazz, String foreignKey, DateTime now) { + Class clazz, String foreignKey, Instant now) { return (Optional) foreignKeyToResourceCache .get(VKey.create(clazz, foreignKey)) - .filter(e -> now.isBefore(e.getDeletionDateTime())) - .map(e -> e.cloneProjectedAtTime(now)); + .filter(e -> now.isBefore(e.getDeletionTime())) + .map(e -> e.cloneProjectedAtInstant(now)); } } diff --git a/core/src/main/java/google/registry/model/ImmutableObject.java b/core/src/main/java/google/registry/model/ImmutableObject.java index 6682b64a4..1d4526f2c 100644 --- a/core/src/main/java/google/registry/model/ImmutableObject.java +++ b/core/src/main/java/google/registry/model/ImmutableObject.java @@ -16,6 +16,7 @@ package google.registry.model; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Maps.transformValues; +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.util.stream.Collectors.toCollection; @@ -29,6 +30,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.Field; +import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; @@ -138,7 +140,11 @@ public abstract class ImmutableObject implements Cloneable { public String toString() { NavigableMap sortedFields = new TreeMap<>(); for (Entry entry : getSignificantFields().entrySet()) { - sortedFields.put(entry.getKey().getName(), entry.getValue()); + Object value = entry.getValue(); + if (value instanceof Instant) { + value = ISO_8601_FORMATTER.format((Instant) value); + } + sortedFields.put(entry.getKey().getName(), value); } return toStringHelper(sortedFields); } @@ -149,9 +155,14 @@ public abstract class ImmutableObject implements Cloneable { NavigableMap sortedFields = new TreeMap<>(); for (Entry entry : getSignificantFields().entrySet()) { Field field = entry.getKey(); - Object value = entry.getValue(); - sortedFields.put( - field.getName(), field.isAnnotationPresent(DoNotHydrate.class) ? value : hydrate(value)); + Object value = + field.isAnnotationPresent(DoNotHydrate.class) + ? entry.getValue() + : hydrate(entry.getValue()); + if (value instanceof Instant) { + value = ISO_8601_FORMATTER.format((Instant) value); + } + sortedFields.put(field.getName(), value); } return toStringHelper(sortedFields); } @@ -174,6 +185,9 @@ public abstract class ImmutableObject implements Cloneable { if (value instanceof ImmutableObject) { return ((ImmutableObject) value).toHydratedString(); } + if (value instanceof Instant) { + return ISO_8601_FORMATTER.format((Instant) value); + } return value; } @@ -211,6 +225,8 @@ public abstract class ImmutableObject implements Cloneable { // We can't use toImmutableList here, because values can be null (especially since the // original ImmutableObject might have been the result of a cloneEmptyToNull call). .collect(toList()); + } else if (o instanceof Instant) { + return ISO_8601_FORMATTER.format((Instant) o); } else if (o instanceof Number || o instanceof Boolean) { return o; } else { diff --git a/core/src/main/java/google/registry/model/ResourceTransferUtils.java b/core/src/main/java/google/registry/model/ResourceTransferUtils.java index 7d4bc12c6..594e64693 100644 --- a/core/src/main/java/google/registry/model/ResourceTransferUtils.java +++ b/core/src/main/java/google/registry/model/ResourceTransferUtils.java @@ -17,6 +17,8 @@ package google.registry.model; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -32,6 +34,7 @@ import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferResponse; import google.registry.model.transfer.TransferResponse.DomainTransferResponse; import google.registry.model.transfer.TransferStatus; +import java.time.Instant; import org.joda.time.DateTime; /** Static utility functions for domain transfers. */ @@ -67,14 +70,28 @@ public final class ResourceTransferUtils { * specified status and date. */ public static PendingActionNotificationResponse createPendingTransferNotificationResponse( - Domain domain, Trid transferRequestTrid, boolean actionResult, DateTime processedDate) { + Domain domain, Trid transferRequestTrid, boolean actionResult, Instant processedDate) { return DomainPendingActionNotificationResponse.create( domain.getDomainName(), actionResult, transferRequestTrid, processedDate); } + /** + * Create a pending action notification response indicating the resolution of a transfer. + * + * @deprecated Use {@link #createPendingTransferNotificationResponse(Domain, Trid, boolean, + * Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static PendingActionNotificationResponse createPendingTransferNotificationResponse( + Domain domain, Trid transferRequestTrid, boolean actionResult, DateTime processedDate) { + return createPendingTransferNotificationResponse( + domain, transferRequestTrid, actionResult, toInstant(processedDate)); + } + /** If there is a transfer out, delete the server-approve entities and enqueue a poll message. */ public static void handlePendingTransferOnDelete( - Domain domain, Domain newDomain, DateTime now, HistoryEntry historyEntry) { + Domain domain, Domain newDomain, Instant now, HistoryEntry historyEntry) { if (!domain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) { return; } @@ -94,6 +111,18 @@ public final class ResourceTransferUtils { .build()); } + /** + * If there is a transfer out, delete the server-approve entities and enqueue a poll message. + * + * @deprecated Use {@link #handlePendingTransferOnDelete(Domain, Domain, Instant, HistoryEntry)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static void handlePendingTransferOnDelete( + Domain domain, Domain newDomain, DateTime now, HistoryEntry historyEntry) { + handlePendingTransferOnDelete(domain, newDomain, toInstant(now), historyEntry); + } + /** * Turn a domain into a builder with its pending transfer resolved. * @@ -102,7 +131,7 @@ public final class ResourceTransferUtils { * sets the expiration time of the last pending transfer to now. */ private static Domain.Builder resolvePendingTransfer( - Domain domain, TransferStatus transferStatus, DateTime now) { + Domain domain, TransferStatus transferStatus, Instant now) { checkArgument( domain.getStatusValues().contains(StatusValue.PENDING_TRANSFER), "Domain is not in pending transfer status."); @@ -116,7 +145,7 @@ public final class ResourceTransferUtils { .getTransferData() .copyConstantFieldsToBuilder() .setTransferStatus(transferStatus) - .setPendingTransferExpirationTime(checkNotNull(now)) + .setPendingTransferExpirationTime(toDateTime(checkNotNull(now))) .build()); } @@ -129,15 +158,27 @@ public final class ResourceTransferUtils { * transfer to now. */ public static Domain approvePendingTransfer( - Domain domain, TransferStatus transferStatus, DateTime now) { + Domain domain, TransferStatus transferStatus, Instant now) { checkArgument(transferStatus.isApproved(), "Not an approval transfer status"); Domain.Builder builder = resolvePendingTransfer(domain, transferStatus, now); return builder - .setLastTransferTime(now) + .setLastTransferTime(toDateTime(now)) .setPersistedCurrentSponsorRegistrarId(domain.getTransferData().getGainingRegistrarId()) .build(); } + /** + * Resolve a pending transfer by awarding it to the gaining client. + * + * @deprecated Use {@link #approvePendingTransfer(Domain, TransferStatus, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static Domain approvePendingTransfer( + Domain domain, TransferStatus transferStatus, DateTime now) { + return approvePendingTransfer(domain, transferStatus, toInstant(now)); + } + /** * Resolve a pending transfer by denying it. * @@ -147,11 +188,23 @@ public final class ResourceTransferUtils { * and sets the last EPP update client id to the given client id. */ public static Domain denyPendingTransfer( - Domain domain, TransferStatus transferStatus, DateTime now, String lastEppUpdateRegistrarId) { + Domain domain, TransferStatus transferStatus, Instant now, String lastEppUpdateRegistrarId) { checkArgument(transferStatus.isDenied(), "Not a denial transfer status"); return resolvePendingTransfer(domain, transferStatus, now) - .setLastEppUpdateTime(now) + .setLastEppUpdateTime(toDateTime(now)) .setLastEppUpdateRegistrarId(lastEppUpdateRegistrarId) .build(); } + + /** + * Resolve a pending transfer by denying it. + * + * @deprecated Use {@link #denyPendingTransfer(Domain, TransferStatus, Instant, String)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static Domain denyPendingTransfer( + Domain domain, TransferStatus transferStatus, DateTime now, String lastEppUpdateRegistrarId) { + return denyPendingTransfer(domain, transferStatus, toInstant(now), lastEppUpdateRegistrarId); + } } diff --git a/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java b/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java index 6d336f819..8cbbe6b97 100644 --- a/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java +++ b/core/src/main/java/google/registry/model/UpdateAutoTimestamp.java @@ -15,12 +15,15 @@ package google.registry.model; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import google.registry.persistence.EntityCallbacksListener.RecursivePrePersist; import google.registry.persistence.EntityCallbacksListener.RecursivePreUpdate; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import java.time.Instant; import java.util.Optional; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -30,7 +33,7 @@ import org.joda.time.DateTime; public class UpdateAutoTimestamp extends ImmutableObject implements UnsafeSerializable { @Column(name = "updateTimestamp") - DateTime lastUpdateTime; + Instant lastUpdateTime; // Unfortunately, we cannot use the @UpdateTimestamp annotation on "lastUpdateTime" in this class // because Hibernate does not allow it to be used on @Embeddable classes, see @@ -38,17 +41,35 @@ public class UpdateAutoTimestamp extends ImmutableObject implements UnsafeSerial @RecursivePrePersist @RecursivePreUpdate public void setTimestamp() { - lastUpdateTime = tm().getTransactionTime(); + lastUpdateTime = tm().getTxTime(); } - /** Returns the timestamp, or {@code START_OF_TIME} if it's null. */ - public DateTime getTimestamp() { - return Optional.ofNullable(lastUpdateTime).orElse(START_OF_TIME); + /** Returns the timestamp, or {@code START_INSTANT} if it's null. */ + public Instant getTimestamp() { + return Optional.ofNullable(lastUpdateTime).orElse(START_INSTANT); } - public static UpdateAutoTimestamp create(@Nullable DateTime timestamp) { + /** + * @deprecated Use {@link #getTimestamp()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getTimestampDateTime() { + return toDateTime(getTimestamp()); + } + + public static UpdateAutoTimestamp create(@Nullable Instant timestamp) { UpdateAutoTimestamp instance = new UpdateAutoTimestamp(); instance.lastUpdateTime = timestamp; return instance; } + + /** + * @deprecated Use {@link #create(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static UpdateAutoTimestamp create(@Nullable DateTime timestamp) { + return create(toInstant(timestamp)); + } } diff --git a/core/src/main/java/google/registry/model/UpdateAutoTimestampEntity.java b/core/src/main/java/google/registry/model/UpdateAutoTimestampEntity.java index 097f121b4..0c229330e 100644 --- a/core/src/main/java/google/registry/model/UpdateAutoTimestampEntity.java +++ b/core/src/main/java/google/registry/model/UpdateAutoTimestampEntity.java @@ -19,6 +19,7 @@ import jakarta.persistence.Access; import jakarta.persistence.AccessType; import jakarta.persistence.MappedSuperclass; import jakarta.xml.bind.annotation.XmlTransient; +import java.time.Instant; /** * Base class for entities that contains an {@link UpdateAutoTimestamp} which is updated every time @@ -38,7 +39,7 @@ public abstract class UpdateAutoTimestampEntity extends ImmutableObject // Prevents subclasses from unexpectedly accessing as property (e.g., Host), which would // require an unnecessary non-private setter method. @Access(AccessType.FIELD) - UpdateAutoTimestamp updateTimestamp = UpdateAutoTimestamp.create(null); + UpdateAutoTimestamp updateTimestamp = UpdateAutoTimestamp.create((Instant) null); /** Get the {@link UpdateAutoTimestamp} for this entity. */ public UpdateAutoTimestamp getUpdateTimestamp() { @@ -62,7 +63,7 @@ public abstract class UpdateAutoTimestampEntity extends ImmutableObject * object being persisted. */ protected void resetUpdateTimestamp() { - this.updateTimestamp = UpdateAutoTimestamp.create(null); + this.updateTimestamp = UpdateAutoTimestamp.create((Instant) null); } /** diff --git a/core/src/main/java/google/registry/model/billing/BillingBase.java b/core/src/main/java/google/registry/model/billing/BillingBase.java index d0463052e..73ab85512 100644 --- a/core/src/main/java/google/registry/model/billing/BillingBase.java +++ b/core/src/main/java/google/registry/model/billing/BillingBase.java @@ -17,6 +17,8 @@ package google.registry.model.billing; import static com.google.common.base.Preconditions.checkNotNull; import static google.registry.util.CollectionUtils.forceEmptyToNull; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.collect.ImmutableSet; @@ -33,6 +35,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; +import java.time.Instant; import java.util.Set; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -143,7 +146,7 @@ public abstract class BillingBase extends ImmutableObject /** When this event was created. For recurrence events, this is also the recurrence start time. */ @Column(nullable = false) - DateTime eventTime; + Instant eventTime; /** The reason for the bill. */ @Enumerated(EnumType.STRING) @@ -170,7 +173,16 @@ public abstract class BillingBase extends ImmutableObject return domainRepoId; } + /** + * @deprecated Use {@link #getEventTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getEventTime() { + return toDateTime(eventTime); + } + + public Instant getEventTimeInstant() { return eventTime; } @@ -226,7 +238,16 @@ public abstract class BillingBase extends ImmutableObject return thisCastToDerived(); } + /** + * @deprecated Use {@link #setEventTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public B setEventTime(DateTime eventTime) { + return setEventTime(toInstant(eventTime)); + } + + public B setEventTime(Instant eventTime) { getInstance().eventTime = eventTime; return thisCastToDerived(); } 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 8af50c950..e821c98f9 100644 --- a/core/src/main/java/google/registry/model/billing/BillingEvent.java +++ b/core/src/main/java/google/registry/model/billing/BillingEvent.java @@ -17,6 +17,8 @@ package google.registry.model.billing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import google.registry.model.domain.DomainHistory; import google.registry.model.domain.token.AllocationToken; @@ -29,6 +31,7 @@ import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.Index; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Optional; import javax.annotation.Nullable; import org.joda.money.Money; @@ -61,7 +64,7 @@ public class BillingEvent extends BillingBase { Money cost; /** When the cost should be billed. */ - DateTime billingTime; + Instant billingTime; /** * The period in years of the action being billed for, if applicable, otherwise null. Used for @@ -75,7 +78,7 @@ public class BillingEvent extends BillingBase { * needs to be undone, a query on this field will return the complete set of potentially bad * events. */ - DateTime syntheticCreationTime; + Instant syntheticCreationTime; /** * For {@link Flag#SYNTHETIC} events, a {@link VKey} to the {@link BillingRecurrence} from which @@ -105,7 +108,16 @@ public class BillingEvent extends BillingBase { return cost; } + /** + * @deprecated Use {@link #getBillingTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getBillingTime() { + return toDateTime(billingTime); + } + + public Instant getBillingTimeInstant() { return billingTime; } @@ -113,7 +125,16 @@ public class BillingEvent extends BillingBase { return periodYears; } + /** + * @deprecated Use {@link #getSyntheticCreationTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getSyntheticCreationTime() { + return toDateTime(syntheticCreationTime); + } + + public Instant getSyntheticCreationTimeInstant() { return syntheticCreationTime; } @@ -165,12 +186,30 @@ public class BillingEvent extends BillingBase { return this; } + /** + * @deprecated Use {@link #setBillingTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setBillingTime(DateTime billingTime) { + return setBillingTime(toInstant(billingTime)); + } + + public Builder setBillingTime(Instant billingTime) { getInstance().billingTime = billingTime; return this; } + /** + * @deprecated Use {@link #setSyntheticCreationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setSyntheticCreationTime(DateTime syntheticCreationTime) { + return setSyntheticCreationTime(toInstant(syntheticCreationTime)); + } + + public Builder setSyntheticCreationTime(Instant syntheticCreationTime) { getInstance().syntheticCreationTime = syntheticCreationTime; return this; } @@ -197,7 +236,7 @@ public class BillingEvent extends BillingBase { checkState(!instance.cost.isNegative(), "Costs should be non-negative."); // TODO(mcilwain): Enforce this check on all billing events (not just more recent ones) // post-migration after we add the missing period years values in SQL. - if (instance.eventTime.isAfter(DateTime.parse("2019-01-01T00:00:00Z"))) { + if (instance.eventTime.isAfter(Instant.parse("2019-01-01T00:00:00Z"))) { checkState( instance.reason.hasPeriodYears() == (instance.periodYears != null), "Period years must be set if and only if reason is " diff --git a/core/src/main/java/google/registry/model/billing/BillingRecurrence.java b/core/src/main/java/google/registry/model/billing/BillingRecurrence.java index 5eb3a8970..58712eea5 100644 --- a/core/src/main/java/google/registry/model/billing/BillingRecurrence.java +++ b/core/src/main/java/google/registry/model/billing/BillingRecurrence.java @@ -16,7 +16,10 @@ package google.registry.model.billing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.minusYears; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import google.registry.model.common.TimeOfYear; import google.registry.persistence.VKey; @@ -30,6 +33,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Index; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Optional; import javax.annotation.Nullable; import org.joda.money.Money; @@ -61,17 +65,17 @@ public class BillingRecurrence extends BillingBase { * The billing event recurs every year between {@link #eventTime} and this time on the [month, * day, time] specified in {@link #recurrenceTimeOfYear}. */ - DateTime recurrenceEndTime; + Instant recurrenceEndTime; /** - * The most recent {@link DateTime} when this recurrence was expanded. + * The most recent {@link Instant} when this recurrence was expanded. * *

We only bother checking recurrences for potential expansion if this is at least one year in * the past. If it's more recent than that, it means that the recurrence was already expanded too * recently to need to be checked again (as domains autorenew each year). */ @Column(nullable = false) - DateTime recurrenceLastExpansion; + Instant recurrenceLastExpansion; /** * The eventTime recurs every year on this [month, day, time] between {@link #eventTime} and @@ -109,11 +113,29 @@ public class BillingRecurrence extends BillingBase { @Column(name = "renewalPriceBehavior", nullable = false) RenewalPriceBehavior renewalPriceBehavior = RenewalPriceBehavior.DEFAULT; + /** + * @deprecated Use {@link #getRecurrenceEndTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getRecurrenceEndTime() { + return toDateTime(recurrenceEndTime); + } + + public Instant getRecurrenceEndTimeInstant() { return recurrenceEndTime; } + /** + * @deprecated Use {@link #getRecurrenceLastExpansionInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getRecurrenceLastExpansion() { + return toDateTime(recurrenceLastExpansion); + } + + public Instant getRecurrenceLastExpansionInstant() { return recurrenceLastExpansion; } @@ -155,12 +177,30 @@ public class BillingRecurrence extends BillingBase { super(instance); } + /** + * @deprecated Use {@link #setRecurrenceEndTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setRecurrenceEndTime(DateTime recurrenceEndTime) { + return setRecurrenceEndTime(toInstant(recurrenceEndTime)); + } + + public Builder setRecurrenceEndTime(Instant recurrenceEndTime) { getInstance().recurrenceEndTime = recurrenceEndTime; return this; } + /** + * @deprecated Use {@link #setRecurrenceLastExpansion(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setRecurrenceLastExpansion(DateTime recurrenceLastExpansion) { + return setRecurrenceLastExpansion(toInstant(recurrenceLastExpansion)); + } + + public Builder setRecurrenceLastExpansion(Instant recurrenceLastExpansion) { getInstance().recurrenceLastExpansion = recurrenceLastExpansion; return this; } @@ -187,15 +227,15 @@ public class BillingRecurrence extends BillingBase { // ensures that it will be expanded on 2/28 next year and included in the February invoice. instance.recurrenceLastExpansion = Optional.ofNullable(instance.recurrenceLastExpansion) - .orElse(instance.eventTime.minusYears(1)); + .orElse(minusYears(instance.eventTime, 1)); checkArgument( instance.renewalPriceBehavior == RenewalPriceBehavior.SPECIFIED ^ instance.renewalPrice == null, "Renewal price can have a value if and only if the renewal price behavior is" + " SPECIFIED"); - instance.recurrenceTimeOfYear = TimeOfYear.fromDateTime(instance.eventTime); + instance.recurrenceTimeOfYear = TimeOfYear.fromInstant(instance.eventTime); instance.recurrenceEndTime = - Optional.ofNullable(instance.recurrenceEndTime).orElse(END_OF_TIME); + Optional.ofNullable(instance.recurrenceEndTime).orElse(END_INSTANT); return super.build(); } } 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 554f21722..57ce27d5f 100644 --- a/core/src/main/java/google/registry/model/common/Cursor.java +++ b/core/src/main/java/google/registry/model/common/Cursor.java @@ -16,7 +16,9 @@ package google.registry.model.common; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import google.registry.model.ImmutableObject; import google.registry.model.UnsafeSerializable; @@ -31,6 +33,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.IdClass; +import java.time.Instant; import java.util.Optional; import org.joda.time.DateTime; @@ -123,7 +126,7 @@ public class Cursor extends UpdateAutoTimestampEntity { String scope; @Column(nullable = false) - DateTime cursorTime = START_OF_TIME; + Instant cursorTime = START_INSTANT; @Override public VKey createVKey() { @@ -143,10 +146,18 @@ public class Cursor extends UpdateAutoTimestampEntity { return VKey.create(Cursor.class, new CursorId(type, scope)); } - public DateTime getLastUpdateTime() { + public Instant getLastUpdateTime() { return getUpdateTimestamp().getTimestamp(); } + /** + * @deprecated Use {@link #getLastUpdateTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getLastUpdateDateTime() { + return toDateTime(getUpdateTimestamp().getTimestamp()); + } public String getScope() { return scope; @@ -168,21 +179,39 @@ public class Cursor extends UpdateAutoTimestampEntity { } /** Creates a new global cursor instance. */ - public static Cursor createGlobal(CursorType cursorType, DateTime cursorTime) { + public static Cursor createGlobal(CursorType cursorType, Instant cursorTime) { return create(cursorType, cursorTime, GLOBAL); } + /** + * @deprecated Use {@link #createGlobal(CursorType, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static Cursor createGlobal(CursorType cursorType, DateTime cursorTime) { + return createGlobal(cursorType, toInstant(cursorTime)); + } + /** Creates a new cursor instance with a given {@link Tld} scope. */ - public static Cursor createScoped(CursorType cursorType, DateTime cursorTime, Tld scope) { + public static Cursor createScoped(CursorType cursorType, Instant cursorTime, Tld scope) { checkNotNull(scope, "Cursor scope cannot be null"); return create(cursorType, cursorTime, scope.getTldStr()); } + /** + * @deprecated Use {@link #createScoped(CursorType, Instant, Tld)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static Cursor createScoped(CursorType cursorType, DateTime cursorTime, Tld scope) { + return createScoped(cursorType, toInstant(cursorTime), scope); + } + /** * Creates a new cursor instance with a given TLD scope, or global if the scope is {@link * #GLOBAL}. */ - private static Cursor create(CursorType cursorType, DateTime cursorTime, String scope) { + private static Cursor create(CursorType cursorType, Instant cursorTime, String scope) { checkNotNull(cursorTime, "Cursor time cannot be null"); checkValidCursorTypeForScope(cursorType, scope); Cursor instance = new Cursor(); @@ -193,13 +222,31 @@ public class Cursor extends UpdateAutoTimestampEntity { } /** - * Returns the current time for a given cursor, or {@code START_OF_TIME} if the cursor is null. + * Returns the current time for a given cursor, or {@code START_INSTANT} if the cursor is null. */ - public static DateTime getCursorTimeOrStartOfTime(Optional cursor) { - return cursor.map(Cursor::getCursorTime).orElse(START_OF_TIME); + public static Instant getCursorTimeOrStartOfTimeInstant(Optional cursor) { + return cursor.map(Cursor::getCursorTimeInstant).orElse(START_INSTANT); } + /** + * @deprecated Use {@link #getCursorTimeOrStartOfTimeInstant(Optional)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static DateTime getCursorTimeOrStartOfTime(Optional cursor) { + return toDateTime(getCursorTimeOrStartOfTimeInstant(cursor)); + } + + /** + * @deprecated Use {@link #getCursorTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getCursorTime() { + return toDateTime(cursorTime); + } + + public Instant getCursorTimeInstant() { return cursorTime; } diff --git a/core/src/main/java/google/registry/model/common/FeatureFlag.java b/core/src/main/java/google/registry/model/common/FeatureFlag.java index bbc18b8f9..4af8eb66d 100644 --- a/core/src/main/java/google/registry/model/common/FeatureFlag.java +++ b/core/src/main/java/google/registry/model/common/FeatureFlag.java @@ -20,6 +20,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration; import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toInstant; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -31,6 +32,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; import google.registry.model.Buildable; import google.registry.model.CacheUtils; import google.registry.model.EntityYamlUtils.TimedTransitionPropertyFeatureStatusDeserializer; @@ -42,6 +44,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; +import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -180,6 +183,10 @@ public class FeatureFlag extends ImmutableObject implements Buildable { } public FeatureStatus getStatus(DateTime time) { + return getStatus(toInstant(time)); + } + + public FeatureStatus getStatus(Instant time) { return status.getValueAtTime(time); } @@ -188,7 +195,7 @@ public class FeatureFlag extends ImmutableObject implements Buildable { */ public static boolean isActiveNow(FeatureName featureName) { tm().assertInTransaction(); - return isActiveAt(featureName, tm().getTransactionTime()); + return isActiveAt(featureName, tm().getTxTime()); } /** @@ -196,11 +203,19 @@ public class FeatureFlag extends ImmutableObject implements Buildable { * doesn't exist. */ public static boolean isActiveAt(FeatureName featureName, DateTime dateTime) { + return isActiveAt(featureName, toInstant(dateTime)); + } + + /** + * Returns whether the flag is active at the given time, or else the flag's default value if it + * doesn't exist. + */ + public static boolean isActiveAt(FeatureName featureName, Instant instant) { tm().assertInTransaction(); return CACHE .get(featureName) - .map(flag -> flag.getStatus(dateTime).equals(ACTIVE)) - .orElse(featureName.getDefaultStatus().equals(ACTIVE)); + .map(flag -> flag.getStatus(instant).equals(ACTIVE)) + .orElseGet(() -> featureName.getDefaultStatus().equals(ACTIVE)); } @Override @@ -230,7 +245,15 @@ public class FeatureFlag extends ImmutableObject implements Buildable { } public Builder setStatusMap(ImmutableSortedMap statusMap) { - getInstance().status = TimedTransitionProperty.fromValueMap(statusMap); + return setStatusMapInstant( + statusMap.entrySet().stream() + .collect( + ImmutableSortedMap.toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue))); + } + + public Builder setStatusMapInstant(ImmutableSortedMap statusMap) { + getInstance().status = TimedTransitionProperty.fromValueMapInstant(statusMap); return this; } } 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 b84f45b7e..2db439c59 100644 --- a/core/src/main/java/google/registry/model/common/TimeOfYear.java +++ b/core/src/main/java/google/registry/model/common/TimeOfYear.java @@ -16,10 +16,16 @@ package google.registry.model.common; import static com.google.common.collect.DiscreteDomain.integers; import static com.google.common.collect.ImmutableList.toImmutableList; +import static google.registry.util.DateTimeUtils.END_INSTANT; import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.isAtOrAfter; import static google.registry.util.DateTimeUtils.isBeforeOrAt; +import static google.registry.util.DateTimeUtils.minusYears; +import static google.registry.util.DateTimeUtils.plusYears; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static org.joda.time.DateTimeZone.UTC; import com.google.common.base.Splitter; @@ -28,6 +34,11 @@ import com.google.common.collect.Range; import google.registry.model.ImmutableObject; import google.registry.model.UnsafeSerializable; import jakarta.persistence.Embeddable; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.List; import org.joda.time.DateTime; @@ -56,25 +67,44 @@ public class TimeOfYear extends ImmutableObject implements UnsafeSerializable { * *

This handles leap years in an intentionally peculiar way by always treating February 29 as * February 28. It is impossible to construct a {@link TimeOfYear} for February 29th. + * + * @deprecated Use {@link #fromInstant(Instant)} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static TimeOfYear fromDateTime(DateTime dateTime) { - DateTime nextYear = dateTime.plusYears(1); // This turns February 29 into February 28. + return fromInstant(toInstant(dateTime)); + } + + /** + * Constructs a {@link TimeOfYear} from an {@link Instant}. + * + *

This handles leap years in an intentionally peculiar way by always treating February 29 as + * February 28. It is impossible to construct a {@link TimeOfYear} for February 29th. + */ + public static TimeOfYear fromInstant(Instant instant) { + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); + int month = zdt.getMonthValue(); + int day = zdt.getDayOfMonth(); + if (month == 2 && day == 29) { + day = 28; + } TimeOfYear instance = new TimeOfYear(); - instance.timeString = String.format( - "%02d %02d %08d", - nextYear.getMonthOfYear(), - nextYear.getDayOfMonth(), - nextYear.getMillisOfDay()); + instance.timeString = + String.format("%02d %02d %08d", month, day, zdt.toLocalTime().toNanoOfDay() / 1000000); return instance; } /** - * Returns an {@link Iterable} of {@link DateTime}s of every recurrence of this particular - * time of year within a given {@link Range} (usually one spanning many years). + * Returns an {@link Iterable} of {@link DateTime}s of every recurrence of this particular time of + * year within a given {@link Range} (usually one spanning many years). * - *

WARNING: This can return a potentially very large {@link Iterable} if {@code END_OF_TIME} - * is used as the upper endpoint of the range. + *

WARNING: This can return a potentially very large {@link Iterable} if {@code END_OF_TIME} is + * used as the upper endpoint of the range. + * + * @deprecated Use {@link #getInstancesInRangeInstant(Range)} */ + @Deprecated public Iterable getInstancesInRange(Range range) { // In registry world, all dates are within START_OF_TIME and END_OF_TIME, so restrict any // ranges without bounds to our notion of zero-to-infinity. @@ -89,16 +119,77 @@ public class TimeOfYear extends ImmutableObject implements UnsafeSerializable { .collect(toImmutableList()); } - /** Get the first {@link DateTime} with this month/day/millis that is at or after the start. */ - public DateTime getNextInstanceAtOrAfter(DateTime start) { - DateTime withSameYear = getDateTimeWithYear(start.getYear()); - return isAtOrAfter(withSameYear, start) ? withSameYear : withSameYear.plusYears(1); + /** + * Returns an {@link Iterable} of {@link Instant}s of every recurrence of this particular time of + * year within a given {@link Range} (usually one spanning many years). + * + *

WARNING: This can return a potentially very large {@link Iterable} if {@code END_INSTANT} is + * used as the upper endpoint of the range. + */ + public Iterable getInstancesInRangeInstant(Range range) { + // In registry world, all dates are within START_INSTANT and END_INSTANT, so restrict any + // ranges without bounds to our notion of zero-to-infinity. + Range normalizedRange = range.intersection(Range.closed(START_INSTANT, END_INSTANT)); + Range yearRange = + Range.closed( + ZonedDateTime.ofInstant(normalizedRange.lowerEndpoint(), java.time.ZoneOffset.UTC) + .getYear(), + ZonedDateTime.ofInstant(normalizedRange.upperEndpoint(), java.time.ZoneOffset.UTC) + .getYear()); + return ContiguousSet.create(yearRange, integers()).stream() + .map(this::toInstantWithYear) + .filter(normalizedRange) + .collect(toImmutableList()); } - /** Get the first {@link DateTime} with this month/day/millis that is at or before the end. */ + /** + * Return a new instant with the same year as the parameter but projected to the month, day, and + * time of day of this object. + */ + private Instant toInstantWithYear(int year) { + List monthDayMillis = Splitter.on(' ').splitToList(timeString); + int month = Integer.parseInt(monthDayMillis.get(0)); + int day = Integer.parseInt(monthDayMillis.get(1)); + int millis = Integer.parseInt(monthDayMillis.get(2)); + return LocalDate.of(year, month, day) + .atTime(LocalTime.ofNanoOfDay(millis * 1000000L)) + .toInstant(ZoneOffset.UTC); + } + + /** + * Get the first {@link DateTime} with this month/day/millis that is at or after the start. + * + * @deprecated Use {@link #getNextInstanceAtOrAfterInstant(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getNextInstanceAtOrAfter(DateTime start) { + return toDateTime(getNextInstanceAtOrAfterInstant(toInstant(start))); + } + + /** Get the first {@link Instant} with this month/day/millis that is at or after the start. */ + public Instant getNextInstanceAtOrAfterInstant(Instant start) { + Instant withSameYear = + toInstantWithYear(ZonedDateTime.ofInstant(start, java.time.ZoneOffset.UTC).getYear()); + return isAtOrAfter(withSameYear, start) ? withSameYear : plusYears(withSameYear, 1); + } + + /** + * Get the first {@link DateTime} with this month/day/millis that is at or before the end. + * + * @deprecated Use {@link #getLastInstanceBeforeOrAtInstant(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastInstanceBeforeOrAt(DateTime end) { - DateTime withSameYear = getDateTimeWithYear(end.getYear()); - return isBeforeOrAt(withSameYear, end) ? withSameYear : withSameYear.minusYears(1); + return toDateTime(getLastInstanceBeforeOrAtInstant(toInstant(end))); + } + + /** Get the first {@link Instant} with this month/day/millis that is at or before the end. */ + public Instant getLastInstanceBeforeOrAtInstant(Instant end) { + Instant withSameYear = + toInstantWithYear(ZonedDateTime.ofInstant(end, java.time.ZoneOffset.UTC).getYear()); + return isBeforeOrAt(withSameYear, end) ? withSameYear : minusYears(withSameYear, 1); } /** 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 3d0d374ca..b32c8089f 100644 --- a/core/src/main/java/google/registry/model/common/TimedTransitionProperty.java +++ b/core/src/main/java/google/registry/model/common/TimedTransitionProperty.java @@ -16,17 +16,22 @@ package google.registry.model.common; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static google.registry.util.CollectionUtils.nullToEmpty; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; +import static google.registry.util.DateTimeUtils.START_INSTANT; import static google.registry.util.DateTimeUtils.latestOf; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; +import com.fasterxml.jackson.annotation.JsonValue; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; import google.registry.model.UnsafeSerializable; import java.io.Serializable; -import java.util.Iterator; -import java.util.NavigableMap; +import java.time.Instant; +import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -34,7 +39,7 @@ import org.joda.time.DateTime; /** * An entity property whose value transitions over time. Each value it takes on becomes active at a * corresponding instant, and remains active until the next transition occurs. At least one "start - * of time" value (corresponding to {@code START_OF_TIME}, i.e. the Unix epoch) must be provided so + * of time" value (corresponding to {@code START_INSTANT}, i.e. the Unix epoch) must be provided so * that the property will have a value for all possible times. */ // Implementation note: this class used to implement the Guava ForwardingMap. This breaks in @@ -45,125 +50,212 @@ public class TimedTransitionProperty implements UnsafeSe private static final long serialVersionUID = -7274659848856323290L; - /** - * Returns a new immutable {@link TimedTransitionProperty} representing the given map of {@link - * DateTime} to value {@link V}. - */ - public static TimedTransitionProperty fromValueMap( - ImmutableSortedMap valueMap) { - checkArgument( - Ordering.natural().equals(valueMap.comparator()), - "Timed transition value map must have transition time keys in chronological order"); - return new TimedTransitionProperty<>(valueMap); - } + /** The map of all the transitions that have been defined for this property. */ + private final ImmutableSortedMap backingMap; /** - * Returns a new immutable {@link TimedTransitionProperty} with an initial value at {@code - * START_OF_TIME}. - */ - public static TimedTransitionProperty withInitialValue( - V initialValue) { - return fromValueMap(ImmutableSortedMap.of(START_OF_TIME, initialValue)); - } - - /** - * Validates a new set of transitions and returns the resulting {@link TimedTransitionProperty}. + * Returns a map of the transitions, with the keys formatted as ISO-8601 strings. * - * @param newTransitions map from {@link DateTime} to transition value {@link V} - * @param allowedTransitions optional map of all possible state-to-state transitions - * @param allowedTransitionMapName optional transition map description string for error messages - * @param initialValue optional initial value; if present, the first transition must have this - * value - * @param badInitialValueErrorMessage option error message string if the initial value is wrong + *

This is used for JSON/YAML serialization. */ - public static TimedTransitionProperty make( - ImmutableSortedMap newTransitions, - ImmutableMultimap allowedTransitions, - String allowedTransitionMapName, - V initialValue, - String badInitialValueErrorMessage) { - validateTimedTransitionMap(newTransitions, allowedTransitions, allowedTransitionMapName); - checkArgument( - newTransitions.firstEntry().getValue() == initialValue, badInitialValueErrorMessage); - return fromValueMap(newTransitions); + @JsonValue + public ImmutableSortedMap getTransitions() { + return backingMap.entrySet().stream() + .collect( + toImmutableSortedMap( + Ordering.natural(), + e -> ISO_8601_FORMATTER.format(e.getKey()), + Map.Entry::getValue)); } - /** - * Validates that a transition map is not null or empty, starts at {@code START_OF_TIME}, and has - * transitions which move from one value to another in allowed ways. - */ - public static void validateTimedTransitionMap( - @Nullable NavigableMap transitionMap, - ImmutableMultimap allowedTransitions, - String mapName) { + private TimedTransitionProperty(ImmutableSortedMap backingMap) { checkArgument( - !nullToEmpty(transitionMap).isEmpty(), "%s map cannot be null or empty.", mapName); - checkArgument( - transitionMap.firstKey().equals(START_OF_TIME), - "%s map must start at START_OF_TIME.", - mapName); - - // Check that all transitions between states are allowed. - Iterator it = transitionMap.values().iterator(); - V currentState = it.next(); - while (it.hasNext()) { - checkArgument( - allowedTransitions.containsKey(currentState), - "%s map cannot transition from %s.", - mapName, - currentState); - V nextState = it.next(); - checkArgument( - allowedTransitions.containsEntry(currentState, nextState), - "%s map cannot transition from %s to %s.", - mapName, - currentState, - nextState); - currentState = nextState; - } - } - - /** The backing map of {@link DateTime} to the value {@link V} that transitions over time. */ - private final ImmutableSortedMap backingMap; - - /** Returns a new {@link TimedTransitionProperty} backed by the provided map instance. */ - private TimedTransitionProperty(NavigableMap backingMap) { - checkArgument( - backingMap.get(START_OF_TIME) != null, + backingMap.containsKey(START_INSTANT), "Must provide transition entry for the start of time (Unix Epoch)"); this.backingMap = ImmutableSortedMap.copyOfSorted(backingMap); } + /** Returns an empty {@link TimedTransitionProperty}. */ + public static TimedTransitionProperty forEmptyMap() { + return new TimedTransitionProperty<>(ImmutableSortedMap.of()); + } + /** - * Checks whether this {@link TimedTransitionProperty} is in a valid state, i.e. whether it has a - * transition entry for {@code START_OF_TIME}, and throws {@link IllegalStateException} if not. + * Returns a {@link TimedTransitionProperty} that starts with the given value at {@code + * START_INSTANT}. */ + public static TimedTransitionProperty withInitialValue(V value) { + return fromValueMapInstant(ImmutableSortedMap.of(START_INSTANT, value)); + } + + /** + * Returns a {@link TimedTransitionProperty} that contains the transition values and times defined + * in the given map. + * + *

The map must contain a value for {@code START_INSTANT}. + * + * @deprecated Use {@link #fromValueMapInstant(ImmutableSortedMap)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static TimedTransitionProperty fromValueMap( + ImmutableSortedMap valueMap) { + return fromValueMapInstant(toInstantMap(valueMap)); + } + + /** + * Returns a {@link TimedTransitionProperty} that contains the transition values and times defined + * in the given map. + * + *

The map must contain a value for {@code START_INSTANT}. + */ + public static TimedTransitionProperty fromValueMapInstant( + ImmutableSortedMap valueMap) { + return new TimedTransitionProperty<>(valueMap); + } + + /** + * Returns a {@link TimedTransitionProperty} that contains the transition values and times defined + * in the given map. + * + *

The map must contain a value for {@code START_OF_TIME}. The map is also validated against a + * set of allowed transitions. + * + * @deprecated Use {@link #makeInstant(ImmutableSortedMap, ImmutableMultimap, String, + * Serializable, String)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static TimedTransitionProperty make( + ImmutableSortedMap valueMap, + ImmutableMultimap allowedTransitions, + String mapName, + V initialValue, + String initialValueErrorMessage) { + return makeInstant( + toInstantMap(valueMap), + allowedTransitions, + mapName, + initialValue, + initialValueErrorMessage); + } + + /** + * Returns a {@link TimedTransitionProperty} that contains the transition values and times defined + * in the given map. + * + *

The map must contain a value for {@code START_INSTANT}. The map is also validated against a + * set of allowed transitions. + */ + public static TimedTransitionProperty makeInstant( + ImmutableSortedMap valueMap, + ImmutableMultimap allowedTransitions, + String mapName, + V initialValue, + String initialValueErrorMessage) { + validateTimedTransitionMapInstant(valueMap, allowedTransitions, mapName); + checkArgument(valueMap.firstEntry().getValue().equals(initialValue), initialValueErrorMessage); + return fromValueMapInstant(valueMap); + } + + /** + * Validates a timed transition map. + * + * @deprecated Use {@link #validateTimedTransitionMapInstant(ImmutableSortedMap, + * ImmutableMultimap, String)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static void validateTimedTransitionMap( + ImmutableSortedMap valueMap, + ImmutableMultimap allowedTransitions, + String mapName) { + validateTimedTransitionMapInstant(toInstantMap(valueMap), allowedTransitions, mapName); + } + + /** Validates a timed transition map. */ + public static void validateTimedTransitionMapInstant( + ImmutableSortedMap valueMap, + ImmutableMultimap allowedTransitions, + String mapName) { + checkArgument( + Ordering.natural().equals(valueMap.comparator()), + "Timed transition value map must have transition time keys in chronological order"); + checkArgument(!valueMap.isEmpty(), "%s map cannot be null or empty.", mapName); + + checkArgument( + valueMap.firstKey().equals(START_INSTANT), "%s map must start at START_OF_TIME.", mapName); + + V lastValue = null; + for (V value : valueMap.values()) { + if (lastValue != null && !allowedTransitions.containsEntry(lastValue, value)) { + if (allowedTransitions.get(lastValue).isEmpty()) { + throw new IllegalArgumentException( + String.format("%s map cannot transition from %s.", mapName, lastValue)); + } else { + throw new IllegalArgumentException( + String.format("%s map cannot transition from %s to %s.", mapName, lastValue, value)); + } + } + lastValue = value; + } + } + + private static ImmutableSortedMap toInstantMap( + ImmutableSortedMap valueMap) { + checkArgument( + Ordering.natural().equals(valueMap.comparator()), + "Timed transition value map must have transition time keys in chronological order"); + return valueMap.entrySet().stream() + .collect( + toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue)); + } + + /** Checks whether the property is valid. */ public void checkValidity() { checkState( - backingMap.get(START_OF_TIME) != null, + backingMap.containsKey(START_INSTANT), "Timed transition values missing required entry for the start of time (Unix Epoch)"); } - /** Exposes the underlying {@link ImmutableSortedMap}. */ + /** Returns the value of the property that is active at the given time. */ + public V getValueAtTime(DateTime time) { + return getValueAtTime(toInstant(time)); + } + + /** Returns the value of the property that is active at the given time. */ + public V getValueAtTime(Instant time) { + return backingMap.floorEntry(latestOf(START_INSTANT, time)).getValue(); + } + + /** Returns the map of all the transitions that have been defined for this property. */ public ImmutableSortedMap toValueMap() { + return backingMap.entrySet().stream() + .collect( + toImmutableSortedMap( + Ordering.natural(), e -> toDateTime(e.getKey()), Map.Entry::getValue)); + } + + /** Returns the map of all the transitions that have been defined for this property. */ + public ImmutableSortedMap toValueMapInstant() { return backingMap; } /** - * Returns the value of the property that is active at the specified time. The active value for a - * time before {@code START_OF_TIME} is extrapolated to be the value that is active at {@code - * START_OF_TIME}. + * Returns the time of the next transition after the given time. Returns null if there is no + * subsequent transition. */ - public V getValueAtTime(DateTime time) { - // Retrieve the current value by finding the latest transition before or at the given time, - // where any given time earlier than START_OF_TIME is replaced by START_OF_TIME. - return backingMap.floorEntry(latestOf(START_OF_TIME, time)).getValue(); + @Nullable + public DateTime getNextTransitionAfter(DateTime time) { + Instant nextTransition = getNextTransitionAfter(toInstant(time)); + return nextTransition == null ? null : toDateTime(nextTransition); } /** Returns the time of the next transition. Returns null if there is no subsequent transition. */ @Nullable - public DateTime getNextTransitionAfter(DateTime time) { - return backingMap.higherKey(latestOf(START_OF_TIME, time)); + public Instant getNextTransitionAfter(Instant time) { + return backingMap.higherKey(latestOf(START_INSTANT, time)); } public int size() { @@ -188,6 +280,8 @@ public class TimedTransitionProperty implements UnsafeSe @Override public String toString() { - return this.backingMap.toString(); + return backingMap.entrySet().stream() + .map(e -> ISO_8601_FORMATTER.format(e.getKey()) + "=" + e.getValue()) + .collect(Collectors.joining(", ", "{", "}")); } } diff --git a/core/src/main/java/google/registry/model/console/PasswordResetRequest.java b/core/src/main/java/google/registry/model/console/PasswordResetRequest.java index d85c7039b..a2d1acae7 100644 --- a/core/src/main/java/google/registry/model/console/PasswordResetRequest.java +++ b/core/src/main/java/google/registry/model/console/PasswordResetRequest.java @@ -14,6 +14,7 @@ package google.registry.model.console; +import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import google.registry.model.Buildable; @@ -27,6 +28,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; +import java.time.Instant; import java.util.Optional; import java.util.UUID; import org.joda.time.DateTime; @@ -57,7 +59,7 @@ public class PasswordResetRequest extends ImmutableObject implements Buildable { name = "creationTime", column = @Column(name = "requestTime", nullable = false)) }) - CreateAutoTimestamp requestTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp requestTime = CreateAutoTimestamp.create((Instant) null); @Column(nullable = false) String requester; @@ -78,10 +80,19 @@ public class PasswordResetRequest extends ImmutableObject implements Buildable { return type; } - public DateTime getRequestTime() { + public Instant getRequestTime() { return requestTime.getTimestamp(); } + /** + * @deprecated Use {@link #getRequestTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getRequestDateTime() { + return toDateTime(requestTime.getTimestamp()); + } + public String getRequester() { return requester; } 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 1f051b567..5b2b0c3bd 100644 --- a/core/src/main/java/google/registry/model/domain/DomainBase.java +++ b/core/src/main/java/google/registry/model/domain/DomainBase.java @@ -28,10 +28,9 @@ import static google.registry.util.CollectionUtils.forceEmptyToNull; import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; import static google.registry.util.CollectionUtils.union; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; import static google.registry.util.DateTimeUtils.earliestOf; import static google.registry.util.DateTimeUtils.isBeforeOrAt; -import static google.registry.util.DateTimeUtils.leapSafeAddYears; import static google.registry.util.DateTimeUtils.plusYears; import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.DateTimeUtils.toInstant; @@ -39,7 +38,6 @@ import static google.registry.util.DomainNameUtils.canonicalizeHostname; import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static java.time.ZoneOffset.UTC; -import static java.time.temporal.ChronoUnit.YEARS; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; @@ -84,7 +82,9 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; import jakarta.persistence.Transient; +import java.time.Duration; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -183,7 +183,7 @@ public class DomainBase extends EppResource { @Expose Set subordinateHosts; /** When this domain's registration will expire. */ - @Expose DateTime registrationExpirationTime; + @Expose Instant registrationExpirationTime; /** * The poll message associated with this domain being deleted. @@ -238,12 +238,12 @@ public class DomainBase extends EppResource { * *

Can be null if the resource has never been transferred. */ - @Expose DateTime lastTransferTime; + @Expose Instant lastTransferTime; /** * When the domain's autorenewal status will expire. * - *

This will be {@link DateTimeUtils#END_OF_TIME} for the vast majority of domains because all + *

This will be {@link DateTimeUtils#END_INSTANT} 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. @@ -255,7 +255,7 @@ public class DomainBase extends EppResource { * 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. */ - DateTime autorenewEndTime; + Instant autorenewEndTime; /** * Which Lordn phase the domain is in after it is created but before the Nordn upload has @@ -290,13 +290,17 @@ public class DomainBase extends EppResource { return nullToEmptyImmutableCopy(subordinateHosts); } + /** + * @deprecated Use {@link #getRegistrationExpirationTime()} + */ @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getRegistrationExpirationDateTime() { - return registrationExpirationTime; + return toDateTime(registrationExpirationTime); } public Instant getRegistrationExpirationTime() { - return toInstant(registrationExpirationTime); + return registrationExpirationTime; } public VKey getDeletePollMessage() { @@ -326,19 +330,37 @@ public class DomainBase extends EppResource { /** * Returns the autorenew end time if there is one, otherwise empty. * - *

Note that {@link DateTimeUtils#END_OF_TIME} is used as a sentinel value in the database + *

Note that {@link DateTimeUtils#END_INSTANT} is used as a sentinel value in the database * representation to signify that autorenew doesn't end, and is mapped to empty here for the * purposes of more legible business logic. + * + * @deprecated Use {@link #getAutorenewEndTimeInstant()} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Optional getAutorenewEndTime() { - return Optional.ofNullable(autorenewEndTime.equals(END_OF_TIME) ? null : autorenewEndTime); + return getAutorenewEndTimeInstant().map(DateTimeUtils::toDateTime); + } + + /** Returns the autorenew end time if there is one, otherwise empty. */ + public Optional getAutorenewEndTimeInstant() { + return Optional.ofNullable(autorenewEndTime.equals(END_INSTANT) ? null : autorenewEndTime); } public DomainTransferData getTransferData() { return Optional.ofNullable(transferData).orElse(DomainTransferData.EMPTY); } + /** + * @deprecated Use {@link #getLastTransferTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastTransferTime() { + return toDateTime(lastTransferTime); + } + + public Instant getLastTransferTimeInstant() { return lastTransferTime; } @@ -507,8 +529,9 @@ public class DomainBase extends EppResource { GracePeriod.create( GracePeriodStatus.TRANSFER, domain.getRepoId(), - toDateTime(transferExpirationTime) - .plus(Tld.get(domain.getTld()).getTransferGracePeriodLength()), + transferExpirationTime.plus( + Duration.ofMillis( + Tld.get(domain.getTld()).getTransferGracePeriodLength().getMillis())), transferData.getGainingRegistrarId(), transferData.getServerApproveBillingEvent()))); } else { @@ -530,12 +553,13 @@ public class DomainBase extends EppResource { Builder builder = domain.asBuilder(); if (isBeforeOrAt(domain.getRegistrationExpirationTime(), now) - && END_OF_TIME.equals(domain.getDeletionDateTime())) { + && END_INSTANT.equals(domain.getDeletionTime())) { // Autorenew by the number of years between the old expiration time and now. Instant lastAutorenewTime = - leapSafeAddYears( + plusYears( domain.getRegistrationExpirationTime(), - YEARS.between(domain.getRegistrationExpirationTime().atZone(UTC), now.atZone(UTC))); + ChronoUnit.YEARS.between( + domain.getRegistrationExpirationTime().atZone(UTC), now.atZone(UTC))); Instant newExpirationTime = plusYears(lastAutorenewTime, 1); builder .setRegistrationExpirationTime(toDateTime(newExpirationTime)) @@ -543,8 +567,9 @@ public class DomainBase extends EppResource { GracePeriod.createForRecurrence( GracePeriodStatus.AUTO_RENEW, domain.getRepoId(), - toDateTime(lastAutorenewTime) - .plus(Tld.get(domain.getTld()).getAutoRenewGracePeriodLength()), + lastAutorenewTime.plus( + Duration.ofMillis( + Tld.get(domain.getTld()).getAutoRenewGracePeriodLength().getMillis())), domain.getCurrentSponsorRegistrarId(), domain.getAutorenewBillingEvent())); newLastEppUpdateTime = Optional.of(lastAutorenewTime); @@ -587,9 +612,8 @@ public class DomainBase extends EppResource { Instant now, Instant currentExpirationTime, @Nullable Integer extendedRegistrationYears) { // We must cap registration at the max years (aka 10), even if that truncates the last year. return earliestOf( - leapSafeAddYears( - currentExpirationTime, Optional.ofNullable(extendedRegistrationYears).orElse(0)), - leapSafeAddYears(now, MAX_REGISTRATION_YEARS)); + plusYears(currentExpirationTime, Optional.ofNullable(extendedRegistrationYears).orElse(0)), + plusYears(now, MAX_REGISTRATION_YEARS)); } /** Loads and returns the fully qualified host names of all linked nameservers. */ @@ -645,8 +669,8 @@ public class DomainBase extends EppResource { } else { // There are nameservers, so make sure INACTIVE isn't there. removeStatusValue(StatusValue.INACTIVE); } - // If there is no autorenew end time, set it to END_OF_TIME. - instance.autorenewEndTime = firstNonNull(getInstance().autorenewEndTime, END_OF_TIME); + // If there is no autorenew end time, set it to END_INSTANT. + instance.autorenewEndTime = firstNonNull(getInstance().autorenewEndTime, END_INSTANT); checkArgumentNotNull(emptyToNull(instance.domainName), "Missing domainName"); instance.tld = getTldFromDomainName(instance.domainName); @@ -741,11 +765,20 @@ public class DomainBase extends EppResource { CollectionUtils.difference(getInstance().getSubordinateHosts(), hostToRemove))); } - public B setRegistrationExpirationTime(DateTime registrationExpirationTime) { + public B setRegistrationExpirationTime(Instant registrationExpirationTime) { getInstance().registrationExpirationTime = registrationExpirationTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setRegistrationExpirationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setRegistrationExpirationTime(DateTime registrationExpirationTime) { + return setRegistrationExpirationTime(toInstant(registrationExpirationTime)); + } + public B setDeletePollMessage(VKey deletePollMessage) { getInstance().deletePollMessage = deletePollMessage; return thisCastToDerived(); @@ -797,8 +830,17 @@ public class DomainBase extends EppResource { * representation to signify that autorenew doesn't end, and is mapped to empty here for the * purposes of more legible business logic. */ + /** + * @deprecated Use {@link #setAutorenewEndTimeInstant(Optional)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public B setAutorenewEndTime(Optional autorenewEndTime) { - getInstance().autorenewEndTime = autorenewEndTime.orElse(END_OF_TIME); + return setAutorenewEndTimeInstant(autorenewEndTime.map(DateTimeUtils::toInstant)); + } + + public B setAutorenewEndTimeInstant(Optional autorenewEndTime) { + getInstance().autorenewEndTime = autorenewEndTime.orElse(END_INSTANT); return thisCastToDerived(); } @@ -807,11 +849,20 @@ public class DomainBase extends EppResource { return thisCastToDerived(); } - public B setLastTransferTime(DateTime lastTransferTime) { + public B setLastTransferTime(Instant lastTransferTime) { getInstance().lastTransferTime = lastTransferTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setLastTransferTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setLastTransferTime(DateTime lastTransferTime) { + return setLastTransferTime(toInstant(lastTransferTime)); + } + public B setCurrentBulkToken(@Nullable VKey currentBulkToken) { if (currentBulkToken == null) { getInstance().currentBulkToken = currentBulkToken; diff --git a/core/src/main/java/google/registry/model/domain/DomainRenewData.java b/core/src/main/java/google/registry/model/domain/DomainRenewData.java index 39f884dc7..66b3bff92 100644 --- a/core/src/main/java/google/registry/model/domain/DomainRenewData.java +++ b/core/src/main/java/google/registry/model/domain/DomainRenewData.java @@ -14,10 +14,16 @@ package google.registry.model.domain; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; + import google.registry.model.eppoutput.EppResponse.ResponseData; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; import org.joda.time.DateTime; /** The {@link ResponseData} returned when renewing a domain. */ @@ -28,12 +34,36 @@ public class DomainRenewData implements ResponseData { String name; @XmlElement(name = "exDate") - DateTime expirationDate; + @XmlJavaTypeAdapter(UtcInstantAdapter.class) + Instant expirationDate; - public static DomainRenewData create(String name, DateTime expirationDate) { + public static DomainRenewData create(String name, Instant expirationDate) { DomainRenewData instance = new DomainRenewData(); instance.name = name; instance.expirationDate = expirationDate; return instance; } + + /** + * @deprecated Use {@link #create(String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static DomainRenewData create(String name, DateTime expirationDate) { + return create(name, toInstant(expirationDate)); + } + + /** Returns the expiration date. */ + public Instant getExpirationDate() { + return expirationDate; + } + + /** + * @deprecated Use {@link #getExpirationDate()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getExpirationDateTime() { + return toDateTime(expirationDate); + } } diff --git a/core/src/main/java/google/registry/model/domain/GracePeriod.java b/core/src/main/java/google/registry/model/domain/GracePeriod.java index 9ff51a82b..64e76a0c6 100644 --- a/core/src/main/java/google/registry/model/domain/GracePeriod.java +++ b/core/src/main/java/google/registry/model/domain/GracePeriod.java @@ -16,6 +16,7 @@ package google.registry.model.domain; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.annotations.VisibleForTesting; @@ -30,6 +31,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.Table; +import java.time.Instant; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -58,7 +60,7 @@ public class GracePeriod extends GracePeriodBase { private static GracePeriod createInternal( GracePeriodStatus type, String domainRepoId, - DateTime expirationTime, + Instant expirationTime, String registrarId, @Nullable VKey billingEvent, @Nullable VKey billingRecurrence, @@ -91,13 +93,29 @@ public class GracePeriod extends GracePeriodBase { public static GracePeriod create( GracePeriodStatus type, String domainRepoId, - DateTime expirationTime, + Instant expirationTime, String registrarId, @Nullable VKey billingEventOneTime) { return createInternal( type, domainRepoId, expirationTime, registrarId, billingEventOneTime, null, null); } + /** + * Creates a GracePeriod for an (optional) OneTime billing event. + * + * @deprecated Use {@link #create(GracePeriodStatus, String, Instant, String, VKey)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static GracePeriod create( + GracePeriodStatus type, + String domainRepoId, + DateTime expirationTime, + String registrarId, + @Nullable VKey billingEventOneTime) { + return create(type, domainRepoId, toInstant(expirationTime), registrarId, billingEventOneTime); + } + /** * Creates a GracePeriod for an (optional) OneTime billing event and a given {@link * #gracePeriodId}. @@ -110,7 +128,7 @@ public class GracePeriod extends GracePeriodBase { public static GracePeriod create( GracePeriodStatus type, String domainRepoId, - DateTime expirationTime, + Instant expirationTime, String registrarId, @Nullable VKey billingEventOneTime, @Nullable Long gracePeriodId) { @@ -118,6 +136,31 @@ public class GracePeriod extends GracePeriodBase { type, domainRepoId, expirationTime, registrarId, billingEventOneTime, null, gracePeriodId); } + /** + * Creates a GracePeriod for an (optional) OneTime billing event and a given {@link + * #gracePeriodId}. + * + * @deprecated Use {@link #create(GracePeriodStatus, String, Instant, String, VKey, Long)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + @VisibleForTesting + public static GracePeriod create( + GracePeriodStatus type, + String domainRepoId, + DateTime expirationTime, + String registrarId, + @Nullable VKey billingEventOneTime, + @Nullable Long gracePeriodId) { + return create( + type, + domainRepoId, + toInstant(expirationTime), + registrarId, + billingEventOneTime, + gracePeriodId); + } + public static GracePeriod createFromHistory(GracePeriodHistory history) { return createInternal( history.type, @@ -133,7 +176,7 @@ public class GracePeriod extends GracePeriodBase { public static GracePeriod createForRecurrence( GracePeriodStatus type, String domainRepoId, - DateTime expirationTime, + Instant expirationTime, String registrarId, VKey billingEventRecurrence) { checkArgumentNotNull(billingEventRecurrence, "billingEventRecurrence cannot be null"); @@ -141,12 +184,29 @@ public class GracePeriod extends GracePeriodBase { type, domainRepoId, expirationTime, registrarId, null, billingEventRecurrence, null); } + /** + * Creates a GracePeriod for a Recurrence billing event. + * + * @deprecated Use {@link #createForRecurrence(GracePeriodStatus, String, Instant, String, VKey)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static GracePeriod createForRecurrence( + GracePeriodStatus type, + String domainRepoId, + DateTime expirationTime, + String registrarId, + VKey billingEventRecurrence) { + return createForRecurrence( + type, domainRepoId, toInstant(expirationTime), registrarId, billingEventRecurrence); + } + /** Creates a GracePeriod for a Recurrence billing event and a given {@link #gracePeriodId}. */ @VisibleForTesting public static GracePeriod createForRecurrence( GracePeriodStatus type, String domainRepoId, - DateTime expirationTime, + Instant expirationTime, String registrarId, VKey billingEventRecurrence, @Nullable Long gracePeriodId) { @@ -161,19 +221,56 @@ public class GracePeriod extends GracePeriodBase { gracePeriodId); } + /** + * Creates a GracePeriod for a Recurrence billing event and a given {@link #gracePeriodId}. + * + * @deprecated Use {@link #createForRecurrence(GracePeriodStatus, String, Instant, String, VKey, + * Long)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + @VisibleForTesting + public static GracePeriod createForRecurrence( + GracePeriodStatus type, + String domainRepoId, + DateTime expirationTime, + String registrarId, + VKey billingEventRecurrence, + @Nullable Long gracePeriodId) { + return createForRecurrence( + type, + domainRepoId, + toInstant(expirationTime), + registrarId, + billingEventRecurrence, + gracePeriodId); + } + /** Creates a GracePeriod with no billing event. */ public static GracePeriod createWithoutBillingEvent( - GracePeriodStatus type, String domainRepoId, DateTime expirationTime, String registrarId) { + GracePeriodStatus type, String domainRepoId, Instant expirationTime, String registrarId) { return createInternal(type, domainRepoId, expirationTime, registrarId, null, null, null); } + /** + * Creates a GracePeriod with no billing event. + * + * @deprecated Use {@link #createWithoutBillingEvent(GracePeriodStatus, String, Instant, String)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static GracePeriod createWithoutBillingEvent( + GracePeriodStatus type, String domainRepoId, DateTime expirationTime, String registrarId) { + return createWithoutBillingEvent(type, domainRepoId, toInstant(expirationTime), registrarId); + } + /** Constructs a GracePeriod of the given type from the provided one-time BillingEvent. */ public static GracePeriod forBillingEvent( GracePeriodStatus type, String domainRepoId, BillingEvent billingEvent) { return create( type, domainRepoId, - billingEvent.getBillingTime(), + billingEvent.getBillingTimeInstant(), billingEvent.getRegistrarId(), billingEvent.createVKey()); } @@ -201,6 +298,7 @@ public class GracePeriod extends GracePeriodBase { return new HistoryEntryId(getDomainRepoId(), domainHistoryRevisionId); } + @SuppressWarnings("InlineMeSuggester") static GracePeriodHistory createFrom(long historyRevisionId, GracePeriod gracePeriod) { GracePeriodHistory instance = new GracePeriodHistory(); instance.gracePeriodHistoryRevisionId = tm().reTransact(tm()::allocateId); @@ -208,7 +306,7 @@ public class GracePeriod extends GracePeriodBase { instance.gracePeriodId = gracePeriod.gracePeriodId; instance.type = gracePeriod.type; instance.domainRepoId = gracePeriod.domainRepoId; - instance.expirationTime = gracePeriod.expirationTime; + instance.expirationTime = gracePeriod.getExpirationTime(); instance.clientId = gracePeriod.clientId; instance.billingEvent = gracePeriod.billingEvent; instance.billingRecurrence = gracePeriod.billingRecurrence; 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 cca7742ed..caf74ce33 100644 --- a/core/src/main/java/google/registry/model/domain/GracePeriodBase.java +++ b/core/src/main/java/google/registry/model/domain/GracePeriodBase.java @@ -14,7 +14,7 @@ package google.registry.model.domain; -import static google.registry.util.DateTimeUtils.toInstant; +import static google.registry.util.DateTimeUtils.toDateTime; import google.registry.model.ImmutableObject; import google.registry.model.UnsafeSerializable; @@ -54,7 +54,7 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab /** When the grace period ends. */ @Column(nullable = false) - DateTime expirationTime; + Instant expirationTime; /** The registrar to bill. */ @Column(name = "registrarId", nullable = false) @@ -94,12 +94,13 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab } @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getExpirationDateTime() { - return expirationTime; + return toDateTime(expirationTime); } public Instant getExpirationTime() { - return toInstant(expirationTime); + return expirationTime; } public String getRegistrarId() { diff --git a/core/src/main/java/google/registry/model/domain/RegistryLock.java b/core/src/main/java/google/registry/model/domain/RegistryLock.java index 5b0ff23cc..590b154cf 100644 --- a/core/src/main/java/google/registry/model/domain/RegistryLock.java +++ b/core/src/main/java/google/registry/model/domain/RegistryLock.java @@ -16,6 +16,8 @@ package google.registry.model.domain; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.util.DateTimeUtils.isBeforeOrAt; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.gson.annotations.Expose; @@ -36,6 +38,7 @@ import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Optional; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -118,7 +121,7 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui column = @Column(name = "lockRequestTime", nullable = false)) }) @Expose - private final CreateAutoTimestamp lockRequestTime = CreateAutoTimestamp.create(null); + private final CreateAutoTimestamp lockRequestTime = CreateAutoTimestamp.create((Instant) null); /** When the unlock is first requested. */ @Expose private DateTime unlockRequestTime; @@ -173,10 +176,19 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui return registryLockEmail; } - public DateTime getLockRequestTime() { + public Instant getLockRequestTime() { return lockRequestTime.getTimestamp(); } + /** + * @deprecated Use {@link #getLockRequestTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getLockRequestDateTime() { + return toDateTime(lockRequestTime.getTimestamp()); + } + /** Returns the unlock request timestamp or null if an unlock has not been requested yet. */ public Optional getUnlockRequestTime() { return Optional.ofNullable(unlockRequestTime); @@ -203,7 +215,7 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui } public DateTime getLastUpdateTime() { - return getUpdateTimestamp().getTimestamp(); + return getUpdateTimestamp().getTimestampDateTime(); } public Long getRevisionId() { @@ -229,18 +241,41 @@ public final class RegistryLock extends UpdateAutoTimestampEntity implements Bui return lockCompletionTime != null && unlockCompletionTime == null; } - /** Returns true iff the lock was requested >= 1 hour ago and has not been verified. */ + /** + * Returns true iff the lock was requested >= 1 hour ago and has not been verified. + * + * @deprecated Use {@link #isLockRequestExpired(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public boolean isLockRequestExpired(DateTime now) { + return isLockRequestExpired(toInstant(now)); + } + + /** Returns true iff the lock was requested >= 1 hour ago and has not been verified. */ + public boolean isLockRequestExpired(Instant now) { return getLockCompletionTime().isEmpty() - && isBeforeOrAt(getLockRequestTime(), now.minusHours(1)); + && isBeforeOrAt(getLockRequestTime(), now.minus(java.time.Duration.ofHours(1))); + } + + /** + * Returns true iff the unlock was requested >= 1 hour ago and has not been verified. + * + * @deprecated Use {@link #isUnlockRequestExpired(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public boolean isUnlockRequestExpired(DateTime now) { + return isUnlockRequestExpired(toInstant(now)); } /** Returns true iff the unlock was requested >= 1 hour ago and has not been verified. */ - public boolean isUnlockRequestExpired(DateTime now) { + public boolean isUnlockRequestExpired(Instant now) { Optional unlockRequestTimestamp = getUnlockRequestTime(); return unlockRequestTimestamp.isPresent() && getUnlockCompletionTime().isEmpty() - && isBeforeOrAt(unlockRequestTimestamp.get(), now.minusHours(1)); + && isBeforeOrAt( + toInstant(unlockRequestTimestamp.get()), now.minus(java.time.Duration.ofHours(1))); } @Override diff --git a/core/src/main/java/google/registry/model/domain/fee/BaseFee.java b/core/src/main/java/google/registry/model/domain/fee/BaseFee.java index 897ecd654..c70511237 100644 --- a/core/src/main/java/google/registry/model/domain/fee/BaseFee.java +++ b/core/src/main/java/google/registry/model/domain/fee/BaseFee.java @@ -18,6 +18,7 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; @@ -29,6 +30,7 @@ import jakarta.xml.bind.annotation.XmlTransient; import jakarta.xml.bind.annotation.XmlValue; import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.math.BigDecimal; +import java.time.Instant; import java.util.stream.Stream; import org.joda.time.DateTime; import org.joda.time.Period; @@ -104,6 +106,8 @@ public abstract class BaseFee extends ImmutableObject { @XmlTransient Range validDateRange; + @XmlTransient Range validDateRangeInstant; + @XmlTransient boolean isPremium; public String getDescription() { @@ -143,13 +147,44 @@ public abstract class BaseFee extends ImmutableObject { return type; } + @JsonIgnore public boolean hasValidDateRange() { - return validDateRange != null; + return validDateRange != null || validDateRangeInstant != null; } + @JsonIgnore public Range getValidDateRange() { checkState(hasValidDateRange()); - return validDateRange; + if (validDateRange != null) { + return validDateRange; + } + return convertRange(validDateRangeInstant, google.registry.util.DateTimeUtils::toDateTime); + } + + @JsonIgnore + public Range getValidDateRangeInstant() { + checkState(hasValidDateRange()); + if (validDateRangeInstant != null) { + return validDateRangeInstant; + } + return convertRange(validDateRange, google.registry.util.DateTimeUtils::toInstant); + } + + private static , T extends Comparable> + Range convertRange(Range range, java.util.function.Function converter) { + if (range.hasLowerBound() && range.hasUpperBound()) { + return Range.range( + converter.apply(range.lowerEndpoint()), + range.lowerBoundType(), + converter.apply(range.upperEndpoint()), + range.upperBoundType()); + } else if (range.hasLowerBound()) { + return Range.downTo(converter.apply(range.lowerEndpoint()), range.lowerBoundType()); + } else if (range.hasUpperBound()) { + return Range.upTo(converter.apply(range.upperEndpoint()), range.upperBoundType()); + } else { + return Range.all(); + } } public boolean hasZeroCost() { diff --git a/core/src/main/java/google/registry/model/domain/fee/Fee.java b/core/src/main/java/google/registry/model/domain/fee/Fee.java index d364a17a3..508c48164 100644 --- a/core/src/main/java/google/registry/model/domain/fee/Fee.java +++ b/core/src/main/java/google/registry/model/domain/fee/Fee.java @@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Range; import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension; import java.math.BigDecimal; +import java.time.Instant; import org.joda.time.DateTime; /** @@ -50,6 +51,18 @@ public class Fee extends BaseFee { return instance; } + /** Creates a Fee for the given cost, type, and valid date range with the default description. */ + public static Fee createInstant( + BigDecimal cost, + FeeType type, + boolean isPremium, + Range validDateRange, + Object... descriptionArgs) { + Fee instance = create(cost, type, isPremium, descriptionArgs); + instance.validDateRangeInstant = validDateRange; + return instance; + } + /** Creates a Fee for the given cost and type with a custom description. */ private static Fee createWithCustomDescription( BigDecimal cost, FeeType type, boolean isPremium, String description) { 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 8c4b6f64b..88b84f30c 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 @@ -17,8 +17,9 @@ package google.registry.model.domain.launch; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.hash.Hashing.crc32; import static com.google.common.io.BaseEncoding.base16; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import com.google.common.base.Ascii; import com.google.common.base.CharMatcher; @@ -31,6 +32,7 @@ import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; import jakarta.xml.bind.annotation.XmlValue; +import java.time.Instant; import java.util.Optional; import org.joda.time.DateTime; @@ -71,20 +73,38 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable NoticeIdType noticeId; @XmlElement(name = "notAfter") - DateTime expirationTime; + Instant expirationTime; @XmlElement(name = "acceptedDate") - DateTime acceptedTime; + Instant acceptedTime; public NoticeIdType getNoticeId() { return Optional.ofNullable(noticeId).orElse(EMPTY_NOTICE_ID); } + /** + * @deprecated Use {@link #getExpirationTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getExpirationTime() { + return toDateTime(expirationTime); + } + + public Instant getExpirationTimeInstant() { return expirationTime; } + /** + * @deprecated Use {@link #getAcceptedTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getAcceptedTime() { + return toDateTime(acceptedTime); + } + + public Instant getAcceptedTimeInstant() { return acceptedTime; } @@ -103,8 +123,7 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable checkArgument(CharMatcher.inRange('0', '9').matchesAllOf(noticeId)); // The checksum in the first 8 chars must match the crc32 of label + expiration + notice id. - String stringToHash = - domainLabel + MILLISECONDS.toSeconds(getExpirationTime().getMillis()) + noticeId; + String stringToHash = domainLabel + getExpirationTimeInstant().getEpochSecond() + noticeId; int computedChecksum = crc32().hashString(stringToHash, UTF_8).asInt(); if (checksum != computedChecksum) { throw new InvalidChecksumException(); @@ -115,7 +134,7 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable public static class InvalidChecksumException extends Exception {} public static LaunchNotice create( - String tcnId, String validatorId, DateTime expirationTime, DateTime acceptedTime) { + String tcnId, String validatorId, Instant expirationTime, Instant acceptedTime) { LaunchNotice instance = new LaunchNotice(); instance.noticeId = new NoticeIdType(); instance.noticeId.tcnId = tcnId; @@ -124,4 +143,14 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable instance.acceptedTime = acceptedTime; return instance; } + + /** + * @deprecated Use {@link #create(String, String, Instant, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static LaunchNotice create( + String tcnId, String validatorId, DateTime expirationTime, DateTime acceptedTime) { + return create(tcnId, validatorId, toInstant(expirationTime), toInstant(acceptedTime)); + } } diff --git a/core/src/main/java/google/registry/model/domain/launch/package-info.java b/core/src/main/java/google/registry/model/domain/launch/package-info.java index 38432a2ca..8c3e765eb 100644 --- a/core/src/main/java/google/registry/model/domain/launch/package-info.java +++ b/core/src/main/java/google/registry/model/domain/launch/package-info.java @@ -17,13 +17,18 @@ xmlns = @XmlNs(prefix = "launch", namespaceURI = "urn:ietf:params:xml:ns:launch-1.0"), elementFormDefault = XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) -@XmlJavaTypeAdapter(UtcDateTimeAdapter.class) +@XmlJavaTypeAdapters({ + @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), + @XmlJavaTypeAdapter(UtcInstantAdapter.class) +}) package google.registry.model.domain.launch; import google.registry.xml.UtcDateTimeAdapter; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlNs; import jakarta.xml.bind.annotation.XmlNsForm; import jakarta.xml.bind.annotation.XmlSchema; import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; diff --git a/core/src/main/java/google/registry/model/domain/package-info.java b/core/src/main/java/google/registry/model/domain/package-info.java index f1f4172cf..c5bc662a1 100644 --- a/core/src/main/java/google/registry/model/domain/package-info.java +++ b/core/src/main/java/google/registry/model/domain/package-info.java @@ -18,12 +18,15 @@ elementFormDefault = XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) @XmlJavaTypeAdapters({ - @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), - @XmlJavaTypeAdapter(DateAdapter.class)}) + @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), + @XmlJavaTypeAdapter(UtcInstantAdapter.class), + @XmlJavaTypeAdapter(DateAdapter.class) +}) package google.registry.model.domain; import google.registry.xml.DateAdapter; import google.registry.xml.UtcDateTimeAdapter; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlNs; diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java index 908f9a46e..d4d004132 100644 --- a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java +++ b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java @@ -26,8 +26,11 @@ import static google.registry.model.domain.token.AllocationToken.TokenType.REGIS import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.forceEmptyToNull; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; @@ -37,6 +40,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Ordering; import com.google.common.collect.Range; import google.registry.flows.EppException; import google.registry.flows.domain.DomainFlowUtils; @@ -60,6 +64,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -208,7 +213,7 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda @Nullable String domainName; /** When this token was created. */ - CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create((Instant) null); /** Allowed registrar client IDs for this token, or null if all registrars are allowed. */ @Column(name = "allowedRegistrarIds") @@ -300,7 +305,7 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda return Optional.ofNullable(domainName); } - public Optional getCreationTime() { + public Optional getCreationTime() { return Optional.ofNullable(creationTime.getTimestamp()); } @@ -334,10 +339,30 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda return tokenType; } + /** + * @deprecated Use {@link #getCreationTime()} + */ + @JsonIgnore + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Optional getCreationDateTime() { + return Optional.ofNullable(toDateTime(creationTime.getTimestamp())); + } + + @JsonIgnore public TimedTransitionProperty getTokenStatusTransitions() { return tokenStatusTransitions; } + public ImmutableSortedMap getTokenStatusTransitionsMap() { + return tokenStatusTransitions.toValueMap(); + } + + @JsonIgnore + public ImmutableSortedMap getTokenStatusTransitionsMapInstant() { + return tokenStatusTransitions.toValueMapInstant(); + } + public ImmutableSet getAllowedEppActions() { return nullToEmptyImmutableCopy(allowedEppActions); } @@ -514,6 +539,11 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda @VisibleForTesting public Builder setCreationTimeForTest(DateTime creationTime) { + return setCreationTimeForTest(toInstant(creationTime)); + } + + @VisibleForTesting + public Builder setCreationTimeForTest(Instant creationTime) { checkState( getInstance().creationTime.getTimestamp() == null, "Creation time can only be set once"); getInstance().creationTime = CreateAutoTimestamp.create(creationTime); @@ -559,8 +589,17 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda public Builder setTokenStatusTransitions( ImmutableSortedMap transitions) { + return setTokenStatusTransitionsInstant( + transitions.entrySet().stream() + .collect( + ImmutableSortedMap.toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue))); + } + + public Builder setTokenStatusTransitionsInstant( + ImmutableSortedMap transitions) { getInstance().tokenStatusTransitions = - TimedTransitionProperty.make( + TimedTransitionProperty.makeInstant( transitions, VALID_TOKEN_STATUS_TRANSITIONS, "tokenStatusTransitions", diff --git a/core/src/main/java/google/registry/model/domain/token/BulkPricingPackage.java b/core/src/main/java/google/registry/model/domain/token/BulkPricingPackage.java index ad0492b12..d35bf0bc2 100644 --- a/core/src/main/java/google/registry/model/domain/token/BulkPricingPackage.java +++ b/core/src/main/java/google/registry/model/domain/token/BulkPricingPackage.java @@ -16,7 +16,9 @@ package google.registry.model.domain.token; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import google.registry.model.Buildable; @@ -31,6 +33,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Optional; import javax.annotation.Nullable; import org.joda.money.Money; @@ -75,13 +78,13 @@ public class BulkPricingPackage extends ImmutableObject implements Buildable { /** The next billing date of the bulk pricing package. */ @Column(nullable = false) - DateTime nextBillingDate = END_OF_TIME; + Instant nextBillingDate = END_INSTANT; /** * Date the last warning email was sent that the bulk pricing package has exceeded the maxDomains * limit. */ - @Nullable DateTime lastNotificationSent; + @Nullable Instant lastNotificationSent; public long getId() { return bulkPricingId; @@ -103,11 +106,29 @@ public class BulkPricingPackage extends ImmutableObject implements Buildable { return bulkPrice; } + /** + * @deprecated Use {@link #getNextBillingDateInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getNextBillingDate() { + return toDateTime(nextBillingDate); + } + + public Instant getNextBillingDateInstant() { return nextBillingDate; } + /** + * @deprecated Use {@link #getLastNotificationSentInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Optional getLastNotificationSent() { + return Optional.ofNullable(toDateTime(lastNotificationSent)); + } + + public Optional getLastNotificationSentInstant() { return Optional.ofNullable(lastNotificationSent); } @@ -175,13 +196,31 @@ public class BulkPricingPackage extends ImmutableObject implements Buildable { return this; } + /** + * @deprecated Use {@link #setNextBillingDate(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setNextBillingDate(DateTime nextBillingDate) { + return setNextBillingDate(toInstant(nextBillingDate)); + } + + public Builder setNextBillingDate(Instant nextBillingDate) { checkArgumentNotNull(nextBillingDate, "Next billing date must not be null"); getInstance().nextBillingDate = nextBillingDate; return this; } + /** + * @deprecated Use {@link #setLastNotificationSent(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setLastNotificationSent(@Nullable DateTime lastNotificationSent) { + return setLastNotificationSent(toInstant(lastNotificationSent)); + } + + public Builder setLastNotificationSent(@Nullable Instant lastNotificationSent) { getInstance().lastNotificationSent = lastNotificationSent; return this; } diff --git a/core/src/main/java/google/registry/model/eppinput/package-info.java b/core/src/main/java/google/registry/model/eppinput/package-info.java index c717d3d84..0ffef1bc2 100644 --- a/core/src/main/java/google/registry/model/eppinput/package-info.java +++ b/core/src/main/java/google/registry/model/eppinput/package-info.java @@ -17,10 +17,18 @@ xmlns = @XmlNs(prefix = "", namespaceURI = "urn:ietf:params:xml:ns:epp-1.0"), elementFormDefault = XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) +@XmlJavaTypeAdapters({ + @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), + @XmlJavaTypeAdapter(UtcInstantAdapter.class) +}) package google.registry.model.eppinput; +import google.registry.xml.UtcDateTimeAdapter; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlNs; import jakarta.xml.bind.annotation.XmlNsForm; import jakarta.xml.bind.annotation.XmlSchema; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; diff --git a/core/src/main/java/google/registry/model/host/HostBase.java b/core/src/main/java/google/registry/model/host/HostBase.java index 66480452b..d2cf03445 100644 --- a/core/src/main/java/google/registry/model/host/HostBase.java +++ b/core/src/main/java/google/registry/model/host/HostBase.java @@ -18,7 +18,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.union; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.DomainNameUtils.canonicalizeHostname; import com.google.common.collect.ImmutableSet; @@ -80,7 +82,7 @@ public class HostBase extends EppResource { * *

Can be null if the resource has never been transferred. */ - DateTime lastTransferTime; + Instant lastTransferTime; /** * The most recent time that the {@link #superordinateDomain} field was changed. @@ -89,7 +91,7 @@ public class HostBase extends EppResource { * to null. This field will be null for new hosts that have never experienced a change of * superordinate domain. */ - DateTime lastSuperordinateChange; + Instant lastSuperordinateChange; public String getHostName() { return hostName; @@ -107,11 +109,29 @@ public class HostBase extends EppResource { return nullToEmptyImmutableCopy(inetAddresses); } + /** + * @deprecated Use {@link #getLastTransferTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastTransferTime() { + return toDateTime(lastTransferTime); + } + + public Instant getLastTransferTimeInstant() { return lastTransferTime; } + /** + * @deprecated Use {@link #getLastSuperordinateChangeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastSuperordinateChange() { + return toDateTime(lastSuperordinateChange); + } + + public Instant getLastSuperordinateChangeInstant() { return lastSuperordinateChange; } @@ -158,21 +178,30 @@ public class HostBase extends EppResource { * {@link #superordinateDomain} field. Passing it as a parameter allows the caller to control * the degree of consistency used to load it. */ - public DateTime computeLastTransferTime(@Nullable Domain superordinateDomain) { + public Instant computeLastTransferTime(@Nullable Domain superordinateDomain) { if (!isSubordinate()) { checkArgument(superordinateDomain == null); - return getLastTransferTime(); + return lastTransferTime; } checkArgument( superordinateDomain != null && superordinateDomain.createVKey().equals(getSuperordinateDomain())); - DateTime lastSuperordinateChange = - Optional.ofNullable(getLastSuperordinateChange()).orElse(getCreationTime()); - DateTime lastTransferOfCurrentSuperordinate = - Optional.ofNullable(superordinateDomain.getLastTransferTime()).orElse(START_OF_TIME); + Instant lastSuperordinateChange = + Optional.ofNullable(getLastSuperordinateChangeInstant()).orElse(getCreationTimeInstant()); + Instant lastTransferOfCurrentSuperordinate = + Optional.ofNullable(superordinateDomain.getLastTransferTimeInstant()).orElse(START_INSTANT); return lastSuperordinateChange.isBefore(lastTransferOfCurrentSuperordinate) - ? superordinateDomain.getLastTransferTime() - : getLastTransferTime(); + ? lastTransferOfCurrentSuperordinate + : lastTransferTime; + } + + /** + * @deprecated Use {@link #computeLastTransferTime(Domain)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime computeLastTransferTimeDateTime(@Nullable Domain superordinateDomain) { + return toDateTime(computeLastTransferTime(superordinateDomain)); } /** A builder for constructing {@link HostBase}, since it is immutable. */ @@ -210,11 +239,20 @@ public class HostBase extends EppResource { return thisCastToDerived(); } - public B setLastSuperordinateChange(DateTime lastSuperordinateChange) { + public B setLastSuperordinateChange(Instant lastSuperordinateChange) { getInstance().lastSuperordinateChange = lastSuperordinateChange; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setLastSuperordinateChange(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setLastSuperordinateChange(DateTime lastSuperordinateChange) { + return setLastSuperordinateChange(toInstant(lastSuperordinateChange)); + } + public B addInetAddresses(ImmutableSet inetAddresses) { return setInetAddresses( ImmutableSet.copyOf(union(getInstance().getInetAddresses(), inetAddresses))); @@ -230,21 +268,30 @@ public class HostBase extends EppResource { return thisCastToDerived(); } - public B setLastTransferTime(DateTime lastTransferTime) { + public B setLastTransferTime(Instant lastTransferTime) { getInstance().lastTransferTime = lastTransferTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setLastTransferTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setLastTransferTime(DateTime lastTransferTime) { + return setLastTransferTime(toInstant(lastTransferTime)); + } + public B copyFrom(HostBase hostBase) { return setCreationRegistrarId(hostBase.getCreationRegistrarId()) - .setCreationTime(hostBase.getCreationTime()) - .setDeletionTime(hostBase.getDeletionDateTime()) + .setCreationTime(hostBase.getCreationTimeInstant()) + .setDeletionTime(hostBase.getDeletionTime()) .setHostName(hostBase.getHostName()) .setInetAddresses(hostBase.getInetAddresses()) - .setLastTransferTime(hostBase.getLastTransferTime()) - .setLastSuperordinateChange(hostBase.getLastSuperordinateChange()) + .setLastTransferTime(hostBase.getLastTransferTimeInstant()) + .setLastSuperordinateChange(hostBase.getLastSuperordinateChangeInstant()) .setLastEppUpdateRegistrarId(hostBase.getLastEppUpdateRegistrarId()) - .setLastEppUpdateTime(hostBase.getLastEppUpdateDateTime()) + .setLastEppUpdateTime(hostBase.getLastEppUpdateTime()) .setPersistedCurrentSponsorRegistrarId(hostBase.getPersistedCurrentSponsorRegistrarId()) .setRepoId(hostBase.getRepoId()) .setSuperordinateDomain(hostBase.getSuperordinateDomain()) diff --git a/core/src/main/java/google/registry/model/host/package-info.java b/core/src/main/java/google/registry/model/host/package-info.java index cb0859056..f53607da5 100644 --- a/core/src/main/java/google/registry/model/host/package-info.java +++ b/core/src/main/java/google/registry/model/host/package-info.java @@ -18,11 +18,14 @@ elementFormDefault = XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) @XmlJavaTypeAdapters({ - @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), - @XmlJavaTypeAdapter(InetAddressAdapter.class)}) + @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), + @XmlJavaTypeAdapter(UtcInstantAdapter.class), + @XmlJavaTypeAdapter(InetAddressAdapter.class) +}) package google.registry.model.host; import google.registry.xml.UtcDateTimeAdapter; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlNs; 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 a8b2028d9..e77b070bd 100644 --- a/core/src/main/java/google/registry/model/poll/PendingActionNotificationResponse.java +++ b/core/src/main/java/google/registry/model/poll/PendingActionNotificationResponse.java @@ -14,11 +14,14 @@ package google.registry.model.poll; +import static google.registry.util.DateTimeUtils.toInstant; + import com.google.common.annotations.VisibleForTesting; import google.registry.model.ImmutableObject; import google.registry.model.UnsafeSerializable; import google.registry.model.eppcommon.Trid; import google.registry.model.eppoutput.EppResponse.ResponseData; +import google.registry.xml.UtcInstantAdapter; import jakarta.persistence.Embeddable; import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlElement; @@ -26,6 +29,8 @@ import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlTransient; import jakarta.xml.bind.annotation.XmlType; import jakarta.xml.bind.annotation.XmlValue; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; import org.joda.time.DateTime; /** The {@link ResponseData} returned when completing a pending action on a domain. */ @@ -51,7 +56,8 @@ public class PendingActionNotificationResponse extends ImmutableObject Trid trid; @XmlElement(name = "paDate") - DateTime processedDate; + @XmlJavaTypeAdapter(UtcInstantAdapter.class) + Instant processedDate; public String getNameAsString() { return nameOrId.value; @@ -68,7 +74,7 @@ public class PendingActionNotificationResponse extends ImmutableObject } protected static T init( - T response, String nameOrId, boolean actionResult, Trid trid, DateTime processedDate) { + T response, String nameOrId, boolean actionResult, Trid trid, Instant processedDate) { response.nameOrId = new NameOrId(); response.nameOrId.value = nameOrId; response.nameOrId.actionResult = actionResult; @@ -91,7 +97,7 @@ public class PendingActionNotificationResponse extends ImmutableObject } public static DomainPendingActionNotificationResponse create( - String domainName, boolean actionResult, Trid trid, DateTime processedDate) { + String domainName, boolean actionResult, Trid trid, Instant processedDate) { return init( new DomainPendingActionNotificationResponse(), domainName, @@ -99,6 +105,16 @@ public class PendingActionNotificationResponse extends ImmutableObject trid, processedDate); } + + /** + * @deprecated Use {@link #create(String, boolean, Trid, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static DomainPendingActionNotificationResponse create( + String domainName, boolean actionResult, Trid trid, DateTime processedDate) { + return create(domainName, actionResult, trid, toInstant(processedDate)); + } } /** An adapter to output the XML in response to resolving a pending command on a contact. */ @@ -115,7 +131,7 @@ public class PendingActionNotificationResponse extends ImmutableObject } public static ContactPendingActionNotificationResponse create( - String contactId, boolean actionResult, Trid trid, DateTime processedDate) { + String contactId, boolean actionResult, Trid trid, Instant processedDate) { return init( new ContactPendingActionNotificationResponse(), contactId, @@ -123,6 +139,16 @@ public class PendingActionNotificationResponse extends ImmutableObject trid, processedDate); } + + /** + * @deprecated Use {@link #create(String, boolean, Trid, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static ContactPendingActionNotificationResponse create( + String contactId, boolean actionResult, Trid trid, DateTime processedDate) { + return create(contactId, actionResult, trid, toInstant(processedDate)); + } } /** An adapter to output the XML in response to resolving a pending command on a host. */ @@ -140,9 +166,19 @@ public class PendingActionNotificationResponse extends ImmutableObject } public static HostPendingActionNotificationResponse create( - String hostName, boolean actionResult, Trid trid, DateTime processedDate) { + String hostName, boolean actionResult, Trid trid, Instant processedDate) { return init( new HostPendingActionNotificationResponse(), hostName, actionResult, trid, processedDate); } + + /** + * @deprecated Use {@link #create(String, boolean, Trid, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static HostPendingActionNotificationResponse create( + String hostName, boolean actionResult, Trid trid, DateTime processedDate) { + return create(hostName, actionResult, trid, toInstant(processedDate)); + } } } 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 161722661..4d0a70c0e 100644 --- a/core/src/main/java/google/registry/model/poll/PollMessage.java +++ b/core/src/main/java/google/registry/model/poll/PollMessage.java @@ -16,7 +16,10 @@ package google.registry.model.poll; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.plusYears; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.collect.ImmutableList; @@ -55,6 +58,7 @@ import jakarta.persistence.Index; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Optional; import org.joda.time.DateTime; @@ -132,7 +136,7 @@ public abstract class PollMessage extends ImmutableObject /** The time when the poll message should be delivered. May be in the future. */ @Column(nullable = false) - DateTime eventTime; + Instant eventTime; /** Human-readable message that will be returned with this poll message. */ @Column(name = "message") @@ -158,7 +162,16 @@ public abstract class PollMessage extends ImmutableObject return clientId; } + /** + * @deprecated Use {@link #getEventTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getEventTime() { + return toDateTime(eventTime); + } + + public Instant getEventTimeInstant() { return eventTime; } @@ -242,7 +255,16 @@ public abstract class PollMessage extends ImmutableObject return thisCastToDerived(); } + /** + * @deprecated Use {@link #setEventTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public B setEventTime(DateTime eventTime) { + return setEventTime(toInstant(eventTime)); + } + + public B setEventTime(Instant eventTime) { getInstance().eventTime = eventTime; return thisCastToDerived(); } @@ -372,7 +394,7 @@ public abstract class PollMessage extends ImmutableObject String domainName; @Column(name = "transfer_response_domain_expiration_time") - DateTime extendedRegistrationExpirationTime; + Instant extendedRegistrationExpirationTime; @Column(name = "transfer_response_contact_id") String contactId; @@ -390,6 +412,19 @@ public abstract class PollMessage extends ImmutableObject return new Builder(clone(this)); } + /** + * @deprecated Use {@link #getExtendedRegistrationExpirationTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getExtendedRegistrationExpirationDateTime() { + return toDateTime(extendedRegistrationExpirationTime); + } + + public Instant getExtendedRegistrationExpirationTime() { + return extendedRegistrationExpirationTime; + } + @Override public ImmutableList getResponseData() { return NullIgnoringCollectionBuilder.create(new ImmutableList.Builder()) @@ -435,9 +470,9 @@ public abstract class PollMessage extends ImmutableObject .setGainingRegistrarId(transferResponse.getGainingRegistrarId()) .setLosingRegistrarId(transferResponse.getLosingRegistrarId()) .setTransferStatus(transferResponse.getTransferStatus()) - .setTransferRequestTime(transferResponse.getTransferRequestTime()) + .setTransferRequestTime(transferResponse.getTransferRequestTimeInstant()) .setPendingTransferExpirationTime( - transferResponse.getPendingTransferExpirationDateTime()) + transferResponse.getPendingTransferExpirationTime()) .setExtendedRegistrationExpirationTime(extendedRegistrationExpirationTime) .build(); } @@ -517,13 +552,22 @@ public abstract class PollMessage extends ImmutableObject String targetId; /** The autorenew recurs annually between {@link #eventTime} and this time. */ - DateTime autorenewEndTime; + Instant autorenewEndTime; public String getTargetId() { return targetId; } + /** + * @deprecated Use {@link #getAutorenewEndTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getAutorenewEndTime() { + return toDateTime(autorenewEndTime); + } + + public Instant getAutorenewEndTimeInstant() { return autorenewEndTime; } @@ -536,7 +580,7 @@ public abstract class PollMessage extends ImmutableObject public ImmutableList getResponseData() { // Note that the event time is when the autorenew occurred, so the expiration time in the // response should be 1 year past that, since it denotes the new expiration time. - return ImmutableList.of(DomainRenewData.create(getTargetId(), getEventTime().plusYears(1))); + return ImmutableList.of(DomainRenewData.create(getTargetId(), plusYears(eventTime, 1))); } @Override @@ -558,7 +602,16 @@ public abstract class PollMessage extends ImmutableObject return this; } + /** + * @deprecated Use {@link #setAutorenewEndTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public Builder setAutorenewEndTime(DateTime autorenewEndTime) { + return setAutorenewEndTime(toInstant(autorenewEndTime)); + } + + public Builder setAutorenewEndTime(Instant autorenewEndTime) { getInstance().autorenewEndTime = autorenewEndTime; return this; } @@ -567,7 +620,7 @@ public abstract class PollMessage extends ImmutableObject public Autorenew build() { Autorenew instance = getInstance(); instance.autorenewEndTime = - Optional.ofNullable(instance.autorenewEndTime).orElse(END_OF_TIME); + Optional.ofNullable(instance.autorenewEndTime).orElse(END_INSTANT); return super.build(); } } diff --git a/core/src/main/java/google/registry/model/poll/package-info.java b/core/src/main/java/google/registry/model/poll/package-info.java index 305c63435..f9f0f124c 100644 --- a/core/src/main/java/google/registry/model/poll/package-info.java +++ b/core/src/main/java/google/registry/model/poll/package-info.java @@ -17,13 +17,18 @@ xmlns = @XmlNs(prefix = "", namespaceURI = "urn:ietf:params:xml:ns:epp-1.0"), elementFormDefault = XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) -@XmlJavaTypeAdapter(UtcDateTimeAdapter.class) +@XmlJavaTypeAdapters({ + @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), + @XmlJavaTypeAdapter(UtcInstantAdapter.class) +}) package google.registry.model.poll; import google.registry.xml.UtcDateTimeAdapter; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlNs; import jakarta.xml.bind.annotation.XmlNsForm; import jakarta.xml.bind.annotation.XmlSchema; import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; 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 9ce2c94b3..b42352e06 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -31,7 +31,9 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PasswordUtils.SALT_SUPPLIER; import static google.registry.util.PasswordUtils.hashPassword; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; @@ -81,6 +83,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.Table; import java.security.cert.CertificateParsingException; +import java.time.Instant; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -399,21 +402,21 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J // Metadata. /** The time when this registrar was created. */ - CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create((Instant) null); /** The time that the certificate was last updated. */ - DateTime lastCertificateUpdateTime; + Instant lastCertificateUpdateTime; /** The time that an expiring certificate notification email was sent to the registrar. */ - DateTime lastExpiringCertNotificationSentDate = START_OF_TIME; + Instant lastExpiringCertNotificationSentDate = START_INSTANT; /** * The time that an expiring failover certificate notification email was sent to the registrar. */ - DateTime lastExpiringFailoverCertNotificationSentDate = START_OF_TIME; + Instant lastExpiringFailoverCertNotificationSentDate = START_INSTANT; /** The time that the POCs have been reviewed last. */ - @Expose DateTime lastPocVerificationDate = START_OF_TIME; + @Expose Instant lastPocVerificationDate = START_INSTANT; /** Telephone support passcode (5-digit numeric) */ String phonePasscode; @@ -434,10 +437,19 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J return registrarId; } - public DateTime getCreationTime() { + public Instant getCreationTime() { return creationTime.getTimestamp(); } + /** + * @deprecated Use {@link #getCreationTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getCreationDateTime() { + return toDateTime(creationTime.getTimestamp()); + } + @Nullable public Long getIanaIdentifier() { return ianaIdentifier; @@ -453,19 +465,55 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J : ImmutableSortedMap.copyOf(billingAccountMap); } + /** + * @deprecated Use {@link #getLastUpdateTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastUpdateTime() { + return getUpdateTimestamp().getTimestampDateTime(); + } + + public Instant getLastUpdateTimeInstant() { return getUpdateTimestamp().getTimestamp(); } + /** + * @deprecated Use {@link #getLastCertificateUpdateTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastCertificateUpdateTime() { + return toDateTime(lastCertificateUpdateTime); + } + + public Instant getLastCertificateUpdateTimeInstant() { return lastCertificateUpdateTime; } + /** + * @deprecated Use {@link #getLastExpiringCertNotificationSentDateInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastExpiringCertNotificationSentDate() { + return toDateTime(lastExpiringCertNotificationSentDate); + } + + public Instant getLastExpiringCertNotificationSentDateInstant() { return lastExpiringCertNotificationSentDate; } + /** + * @deprecated Use {@link #getLastExpiringFailoverCertNotificationSentDateInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastExpiringFailoverCertNotificationSentDate() { + return toDateTime(lastExpiringFailoverCertNotificationSentDate); + } + + public Instant getLastExpiringFailoverCertNotificationSentDateInstant() { return lastExpiringFailoverCertNotificationSentDate; } @@ -473,7 +521,16 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J return registrarName; } + /** + * @deprecated Use {@link #getLastPocVerificationDateInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getLastPocVerificationDate() { + return toDateTime(lastPocVerificationDate); + } + + public Instant getLastPocVerificationDateInstant() { return lastPocVerificationDate; } @@ -793,7 +850,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J return this; } - public Builder setClientCertificate(String clientCertificate, DateTime now) { + public Builder setClientCertificate(String clientCertificate, Instant now) { clientCertificate = emptyToNull(clientCertificate); String clientCertificateHash = calculateHash(clientCertificate); if (!Objects.equals(clientCertificate, getInstance().clientCertificate) @@ -805,20 +862,47 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J return this; } - public Builder setLastExpiringCertNotificationSentDate(DateTime now) { + /** + * @deprecated Use {@link #setClientCertificate(String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setClientCertificate(String clientCertificate, DateTime now) { + return setClientCertificate(clientCertificate, toInstant(now)); + } + + public Builder setLastExpiringCertNotificationSentDate(Instant now) { checkArgumentNotNull(now, "Registrar lastExpiringCertNotificationSentDate cannot be null"); getInstance().lastExpiringCertNotificationSentDate = now; return this; } - public Builder setLastExpiringFailoverCertNotificationSentDate(DateTime now) { + /** + * @deprecated Use {@link #setLastExpiringCertNotificationSentDate(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setLastExpiringCertNotificationSentDate(DateTime now) { + return setLastExpiringCertNotificationSentDate(toInstant(now)); + } + + public Builder setLastExpiringFailoverCertNotificationSentDate(Instant now) { checkArgumentNotNull( now, "Registrar lastExpiringFailoverCertNotificationSentDate cannot be null"); getInstance().lastExpiringFailoverCertNotificationSentDate = now; return this; } - public Builder setFailoverClientCertificate(String clientCertificate, DateTime now) { + /** + * @deprecated Use {@link #setLastExpiringFailoverCertNotificationSentDate(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setLastExpiringFailoverCertNotificationSentDate(DateTime now) { + return setLastExpiringFailoverCertNotificationSentDate(toInstant(now)); + } + + public Builder setFailoverClientCertificate(String clientCertificate, Instant now) { clientCertificate = emptyToNull(clientCertificate); String clientCertificateHash = calculateHash(clientCertificate); if (!Objects.equals(clientCertificate, getInstance().failoverClientCertificate) @@ -830,12 +914,30 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J return this; } - public Builder setLastPocVerificationDate(DateTime now) { + /** + * @deprecated Use {@link #setFailoverClientCertificate(String, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setFailoverClientCertificate(String clientCertificate, DateTime now) { + return setFailoverClientCertificate(clientCertificate, toInstant(now)); + } + + public Builder setLastPocVerificationDate(Instant now) { checkArgumentNotNull(now, "Registrar lastPocVerificationDate cannot be null"); getInstance().lastPocVerificationDate = now; return this; } + /** + * @deprecated Use {@link #setLastPocVerificationDate(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setLastPocVerificationDate(DateTime now) { + return setLastPocVerificationDate(toInstant(now)); + } + private static String calculateHash(String clientCertificate) { if (clientCertificate == null) { return null; @@ -968,11 +1070,21 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J * and breaks the verification that an object has not been updated since it was copied. */ @VisibleForTesting - public Builder setLastUpdateTime(DateTime timestamp) { + public Builder setLastUpdateTime(Instant timestamp) { getInstance().setUpdateTimestamp(UpdateAutoTimestamp.create(timestamp)); return this; } + /** + * @deprecated Use {@link #setLastUpdateTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + @VisibleForTesting + public Builder setLastUpdateTime(DateTime timestamp) { + return setLastUpdateTime(toInstant(timestamp)); + } + /** Build the registrar, nullifying empty fields. */ @Override public Registrar build() { 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 1f4b3ad61..279872fc1 100644 --- a/core/src/main/java/google/registry/model/reporting/DomainTransactionRecord.java +++ b/core/src/main/java/google/registry/model/reporting/DomainTransactionRecord.java @@ -15,6 +15,8 @@ package google.registry.model.reporting; import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.collect.ImmutableSet; @@ -31,6 +33,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.Table; +import java.time.Instant; import org.joda.time.DateTime; /** @@ -81,7 +84,7 @@ public class DomainTransactionRecord extends ImmutableObject * Grace period spec */ @Column(nullable = false) - DateTime reportingTime; + Instant reportingTime; /** The transaction report field we add reportAmount to for this registrar. */ @Column(nullable = false) @@ -189,10 +192,6 @@ public class DomainTransactionRecord extends ImmutableObject return new HistoryEntryId(domainRepoId, historyRevisionId); } - public DateTime getReportingTime() { - return reportingTime; - } - public String getTld() { return tld; } @@ -205,10 +204,23 @@ public class DomainTransactionRecord extends ImmutableObject return reportAmount; } + /** + * @deprecated Use {@link #getReportingTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getReportingTime() { + return toDateTime(reportingTime); + } + + public Instant getReportingTimeInstant() { + return reportingTime; + } + /** An alternative construction method when the builder is not necessary. */ public static DomainTransactionRecord create( String tld, - DateTime reportingTime, + Instant reportingTime, TransactionReportField transactionReportField, int reportAmount) { return new DomainTransactionRecord.Builder() @@ -220,6 +232,19 @@ public class DomainTransactionRecord extends ImmutableObject .build(); } + /** + * @deprecated Use {@link #create(String, Instant, TransactionReportField, int)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static DomainTransactionRecord create( + String tld, + DateTime reportingTime, + TransactionReportField transactionReportField, + int reportAmount) { + return create(tld, toInstant(reportingTime), transactionReportField, reportAmount); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -241,12 +266,21 @@ public class DomainTransactionRecord extends ImmutableObject return this; } - public Builder setReportingTime(DateTime reportingTime) { - checkArgumentNotNull(reportingTime, "reportingTime must not be mull"); + public Builder setReportingTime(Instant reportingTime) { + checkArgumentNotNull(reportingTime, "reportingTime must not be null"); getInstance().reportingTime = reportingTime; return this; } + /** + * @deprecated Use {@link #setReportingTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setReportingTime(DateTime reportingTime) { + return setReportingTime(toInstant(reportingTime)); + } + public Builder setReportField(TransactionReportField reportField) { checkArgumentNotNull(reportField, "reportField must not be null"); getInstance().reportField = reportField; 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 a6000e0ee..c7f1ae656 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java @@ -15,6 +15,8 @@ package google.registry.model.reporting; import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import google.registry.batch.ExpandBillingRecurrencesAction; @@ -41,6 +43,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.MappedSuperclass; +import java.time.Instant; import java.util.Optional; import javax.annotation.Nullable; import org.apache.commons.lang3.BooleanUtils; @@ -138,7 +141,7 @@ public abstract class HistoryEntry extends ImmutableObject /** The time the command occurred, represented by the transaction time. */ @Column(nullable = false, name = "historyModificationTime") - DateTime modificationTime; + protected Instant modificationTime; /** The id of the registrar that sent the command. */ @Column(name = "historyRegistrarId") @@ -197,7 +200,19 @@ public abstract class HistoryEntry extends ImmutableObject return xmlBytes == null ? null : xmlBytes.clone(); } + /** + * Returns the time the command occurred. + * + * @deprecated Use {@link #getModificationTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getModificationTime() { + return toDateTime(modificationTime); + } + + /** Returns the time the command occurred. */ + public Instant getModificationTimeInstant() { return modificationTime; } @@ -307,11 +322,20 @@ public abstract class HistoryEntry extends ImmutableObject return thisCastToDerived(); } - public B setModificationTime(DateTime modificationTime) { + public B setModificationTime(Instant modificationTime) { getInstance().modificationTime = modificationTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setModificationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setModificationTime(DateTime modificationTime) { + return setModificationTime(toInstant(modificationTime)); + } + public B setRegistrarId(String registrarId) { getInstance().clientId = registrarId; return thisCastToDerived(); diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java index 1ff7e09b6..e3834086a 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java @@ -17,8 +17,9 @@ package google.registry.model.reporting; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; @@ -32,7 +33,7 @@ import google.registry.model.host.HostHistory; import google.registry.persistence.VKey; import google.registry.persistence.transaction.CriteriaQueryBuilder; import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; +import java.time.Instant; import java.util.Comparator; import java.util.List; import java.util.stream.Stream; @@ -49,9 +50,21 @@ public class HistoryEntryDao { Host.class, HostHistory.class); - /** Loads all history objects in the times specified, including all types. */ + /** + * Loads all history objects in the times specified, including all types. + * + * @deprecated Use {@link #loadAllHistoryObjects(Instant, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public static ImmutableList loadAllHistoryObjects( DateTime afterTime, DateTime beforeTime) { + return loadAllHistoryObjects(toInstant(afterTime), toInstant(beforeTime)); + } + + /** Loads all history objects in the times specified, including all types. */ + public static ImmutableList loadAllHistoryObjects( + Instant afterTime, Instant beforeTime) { return tm().transact( () -> new ImmutableList.Builder() @@ -63,7 +76,7 @@ public class HistoryEntryDao { /** Loads all history objects corresponding to the given {@link EppResource}. */ public static ImmutableList loadHistoryObjectsForResource( VKey resourceKey) { - return loadHistoryObjectsForResource(resourceKey, START_OF_TIME, END_OF_TIME); + return loadHistoryObjectsForResource(resourceKey, START_INSTANT, END_INSTANT); } /** @@ -72,16 +85,40 @@ public class HistoryEntryDao { */ public static ImmutableList loadHistoryObjectsForResource( VKey resourceKey, Class subclazz) { - return loadHistoryObjectsForResource(resourceKey, START_OF_TIME, END_OF_TIME, subclazz); + return loadHistoryObjectsForResource(resourceKey, START_INSTANT, END_INSTANT, subclazz); + } + + /** + * @deprecated Use {@link #loadHistoryObjectsForResource(VKey, Instant, Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static ImmutableList loadHistoryObjectsForResource( + VKey resourceKey, DateTime afterTime, DateTime beforeTime) { + return loadHistoryObjectsForResource(resourceKey, toInstant(afterTime), toInstant(beforeTime)); } /** Loads all history objects in the time period specified for the given {@link EppResource}. */ public static ImmutableList loadHistoryObjectsForResource( - VKey resourceKey, DateTime afterTime, DateTime beforeTime) { + VKey resourceKey, Instant afterTime, Instant beforeTime) { return tm().transact( () -> loadHistoryObjectsForResourceInternal(resourceKey, afterTime, beforeTime)); } + /** + * @deprecated Use {@link #loadHistoryObjectsForResource(VKey, Instant, Instant, Class)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static ImmutableList loadHistoryObjectsForResource( + VKey resourceKey, + DateTime afterTime, + DateTime beforeTime, + Class subclazz) { + return loadHistoryObjectsForResource( + resourceKey, toInstant(afterTime), toInstant(beforeTime), subclazz); + } + /** * Loads all history objects in the time period specified for the given {@link EppResource} and * cast to the appropriate subclass. @@ -91,11 +128,12 @@ public class HistoryEntryDao { * #getHistoryClassFromParent(Class)} to obtain it, which we also did to confirm that the provided * subclass is indeed correct. */ - private static ImmutableList loadHistoryObjectsForResource( + public static ImmutableList loadHistoryObjectsForResource( VKey resourceKey, - DateTime afterTime, - DateTime beforeTime, + Instant afterTime, + Instant beforeTime, Class subclazz) { + Class expectedSubclazz = getHistoryClassFromParent(resourceKey.getKind()); checkArgument( @@ -132,20 +170,22 @@ public class HistoryEntryDao { } private static ImmutableList loadHistoryObjectsForResourceInternal( - VKey resourceKey, DateTime afterTime, DateTime beforeTime) { + VKey resourceKey, Instant afterTime, Instant beforeTime) { // The class we're searching from is based on which resource type (e.g. Domain) we have Class historyClass = getHistoryClassFromParent(resourceKey.getKind()); - CriteriaBuilder criteriaBuilder = tm().getEntityManager().getCriteriaBuilder(); - CriteriaQuery criteriaQuery = - CriteriaQueryBuilder.create(historyClass) - .where("modificationTime", criteriaBuilder::greaterThanOrEqualTo, afterTime) - .where("modificationTime", criteriaBuilder::lessThanOrEqualTo, beforeTime) - .where("repoId", criteriaBuilder::equal, resourceKey.getKey().toString()) - .orderByAsc("revisionId") - .orderByAsc("modificationTime") - .build(); - - return ImmutableList.copyOf(tm().criteriaQuery(criteriaQuery).getResultList()); + return ImmutableList.copyOf( + tm().getEntityManager() + .createQuery( + String.format( + "FROM %s WHERE repoId = :repoId AND modificationTime >= :afterTime AND" + + " modificationTime <= :beforeTime ORDER BY revisionId ASC," + + " modificationTime ASC", + historyClass.getName()), + historyClass) + .setParameter("repoId", resourceKey.getKey().toString()) + .setParameter("afterTime", afterTime) + .setParameter("beforeTime", beforeTime) + .getResultList()); } public static Class getHistoryClassFromParent( @@ -158,7 +198,7 @@ public class HistoryEntryDao { } private static List loadAllHistoryObjects( - Class historyClass, DateTime afterTime, DateTime beforeTime) { + Class historyClass, Instant afterTime, Instant beforeTime) { CriteriaBuilder criteriaBuilder = tm().getEntityManager().getCriteriaBuilder(); return tm().criteriaQuery( CriteriaQueryBuilder.create(historyClass) diff --git a/core/src/main/java/google/registry/model/tld/Tld.java b/core/src/main/java/google/registry/model/tld/Tld.java index ae18d2011..da973a4d0 100644 --- a/core/src/main/java/google/registry/model/tld/Tld.java +++ b/core/src/main/java/google/registry/model/tld/Tld.java @@ -22,8 +22,10 @@ import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDura import static google.registry.model.EntityYamlUtils.createObjectMapper; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static org.joda.money.CurrencyUnit.USD; @@ -75,6 +77,7 @@ import google.registry.persistence.converter.AllocationTokenVkeyListUserType; import google.registry.persistence.converter.BillingCostTransitionUserType; import google.registry.persistence.converter.TldStateTransitionUserType; import google.registry.tldconfig.idn.IdnTableEnum; +import google.registry.util.DateTimeUtils; import google.registry.util.Idn; import jakarta.persistence.AttributeOverride; import jakarta.persistence.Column; @@ -83,6 +86,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; @@ -258,7 +262,12 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** Checks if {@code tld} is enrolled with BSA. */ public static boolean isEnrolledWithBsa(Tld tld, DateTime now) { - return tld.getBsaEnrollStartTime().orElse(END_OF_TIME).isBefore(now); + return isEnrolledWithBsa(tld, toInstant(now)); + } + + /** Checks if {@code tld} is enrolled with BSA. */ + public static boolean isEnrolledWithBsa(Tld tld, Instant now) { + return tld.getBsaEnrollStartTimeInstant().orElse(END_INSTANT).isBefore(now); } /** @@ -382,7 +391,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** An automatically managed creation timestamp. */ @Column(nullable = false) @JsonDeserialize(using = CreateAutoTimestampDeserializer.class) - CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create((Instant) null); /** The set of reserved list names that are applicable to this tld. */ @JsonSerialize(using = SortedSetSerializer.class) @@ -524,7 +533,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** The end of the claims period (at or after this time, claims no longer applies). */ @Column(nullable = false) - DateTime claimsPeriodEnd = END_OF_TIME; + Instant claimsPeriodEnd = END_INSTANT; /** An allowlist of hosts allowed to be used on domains on this TLD (ignored if empty). */ @Nullable @@ -569,7 +578,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl */ // TODO(b/309175410): implement setup and cleanup procedure for joining or leaving BSA, and see // if it can be integrated with the ConfigTldCommand. - @JsonIgnore @Nullable DateTime bsaEnrollStartTime; + @JsonIgnore @Nullable Instant bsaEnrollStartTime; public String getTldStr() { return tldStr; @@ -593,6 +602,12 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** Returns the time when this TLD was enrolled in the Brand Safety Alliance (BSA) program. */ @JsonIgnore public Optional getBsaEnrollStartTime() { + return Optional.ofNullable(toDateTime(this.bsaEnrollStartTime)); + } + + /** Returns the time when this TLD was enrolled in the Brand Safety Alliance (BSA) program. */ + @JsonIgnore + public Optional getBsaEnrollStartTimeInstant() { return Optional.ofNullable(this.bsaEnrollStartTime); } @@ -607,19 +622,43 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl *

Note that {@link TldState#PDT} TLDs pretend to be in {@link TldState#GENERAL_AVAILABILITY}. */ public TldState getTldState(DateTime now) { + return getTldState(toInstant(now)); + } + + /** + * Retrieve the TLD state at the given time. Defaults to {@link TldState#PREDELEGATION}. + * + *

Note that {@link TldState#PDT} TLDs pretend to be in {@link TldState#GENERAL_AVAILABILITY}. + */ + public TldState getTldState(Instant now) { TldState state = tldStateTransitions.getValueAtTime(now); return TldState.PDT.equals(state) ? TldState.GENERAL_AVAILABILITY : state; } /** Retrieve whether this TLD is in predelegation testing. */ public boolean isPdt(DateTime now) { + return isPdt(toInstant(now)); + } + + /** Retrieve whether this TLD is in predelegation testing. */ + public boolean isPdt(Instant now) { return TldState.PDT.equals(tldStateTransitions.getValueAtTime(now)); } - public DateTime getCreationTime() { + public Instant getCreationTime() { return creationTime.getTimestamp(); } + /** + * @deprecated Use {@link #getCreationTime()} + */ + @JsonIgnore + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getCreationDateTime() { + return toDateTime(creationTime.getTimestamp()); + } + public boolean getEscrowEnabled() { return escrowEnabled; } @@ -678,6 +717,14 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * domain create. */ public Money getCreateBillingCost(DateTime now) { + return getCreateBillingCost(toInstant(now)); + } + + /** + * Use {@code PricingEngineProxy.getDomainCreateCost} instead of this to find the cost for a + * domain create. + */ + public Money getCreateBillingCost(Instant now) { return createBillingCostTransitions.getValueAtTime(now); } @@ -685,6 +732,11 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl return createBillingCostTransitions.toValueMap(); } + @JsonIgnore + public ImmutableSortedMap getCreateBillingCostTransitionsInstant() { + return createBillingCostTransitions.toValueMapInstant(); + } + /** * Returns the add-on cost of a domain restore (the flat tld-wide fee charged in addition to one * year of renewal for that name). @@ -699,6 +751,15 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * restore cost). */ public Money getStandardRenewCost(DateTime now) { + return getStandardRenewCost(toInstant(now)); + } + + /** + * Use {@code PricingEngineProxy.getDomainRenewCost} instead of this to find the cost for a domain + * renewal, and all derived costs (i.e. autorenews, transfers, and the per-domain part of a + * restore cost). + */ + public Money getStandardRenewCost(Instant now) { return renewBillingCostTransitions.getValueAtTime(now); } @@ -716,28 +777,43 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl return tldStateTransitions.toValueMap(); } + @JsonIgnore + public ImmutableSortedMap getTldStateTransitionsInstant() { + return tldStateTransitions.toValueMapInstant(); + } + public ImmutableSortedMap getRenewBillingCostTransitions() { return renewBillingCostTransitions.toValueMap(); } + @JsonIgnore + public ImmutableSortedMap getRenewBillingCostTransitionsInstant() { + return renewBillingCostTransitions.toValueMapInstant(); + } + /** Returns the EAP fee for the tld at the given time. */ public Fee getEapFeeFor(DateTime now) { - ImmutableSortedMap valueMap = getEapFeeScheduleAsMap(); - DateTime periodStart = valueMap.floorKey(now); - DateTime periodEnd = valueMap.ceilingKey(now); - // NOTE: assuming END_OF_TIME would never be reached... - Range validPeriod = + return getEapFeeFor(toInstant(now)); + } + + /** Returns the EAP fee for the tld at the given time. */ + public Fee getEapFeeFor(Instant now) { + ImmutableSortedMap valueMap = getEapFeeScheduleAsMapInstant(); + Instant periodStart = valueMap.floorKey(now); + Instant periodEnd = valueMap.ceilingKey(now); + // NOTE: assuming END_INSTANT would never be reached... + Range validPeriod = Range.closedOpen( - periodStart != null ? periodStart : START_OF_TIME, - periodEnd != null ? periodEnd : END_OF_TIME); - return Fee.create( + periodStart != null ? periodStart : START_INSTANT, + periodEnd != null ? periodEnd : END_INSTANT); + return Fee.createInstant( eapFeeSchedule.getValueAtTime(now).getAmount(), FeeType.EAP, // An EAP fee does not count as premium -- it's a separate one-time fee, independent of // which the domain is separately considered standard vs premium depending on renewal price. false, validPeriod, - validPeriod.upperEndpoint()); + toDateTime(validPeriod.upperEndpoint())); } @VisibleForTesting @@ -746,11 +822,22 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl return eapFeeSchedule.toValueMap(); } + @JsonIgnore + @VisibleForTesting + public ImmutableSortedMap getEapFeeScheduleAsMapInstant() { + return eapFeeSchedule.toValueMapInstant(); + } + public String getLordnUsername() { return lordnUsername; } public DateTime getClaimsPeriodEnd() { + return toDateTime(claimsPeriodEnd); + } + + @JsonIgnore + public Instant getClaimsPeriodEndInstant() { return claimsPeriodEnd; } @@ -828,6 +915,16 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** Sets the TLD state to transition to the specified states at the specified times. */ public Builder setTldStateTransitions(ImmutableSortedMap tldStatesMap) { + return setTldStateTransitionsInstant( + tldStatesMap.entrySet().stream() + .collect( + ImmutableSortedMap.toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue))); + } + + /** Sets the TLD state to transition to the specified states at the specified times. */ + public Builder setTldStateTransitionsInstant( + ImmutableSortedMap tldStatesMap) { checkNotNull(tldStatesMap, "TLD states map cannot be null"); // Filter out any entries with QUIET_PERIOD as the value before checking for ordering, since // that phase is allowed to appear anywhere. @@ -838,7 +935,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl .filter(state -> !TldState.QUIET_PERIOD.equals(state)) .collect(Collectors.toList())), "The TLD states are chronologically out of order"); - getInstance().tldStateTransitions = TimedTransitionProperty.fromValueMap(tldStatesMap); + getInstance().tldStateTransitions = TimedTransitionProperty.fromValueMapInstant(tldStatesMap); return this; } @@ -960,12 +1057,21 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl public Builder setCreateBillingCostTransitions( ImmutableSortedMap createCostsMap) { + return setCreateBillingCostTransitionsInstant( + createCostsMap.entrySet().stream() + .collect( + ImmutableSortedMap.toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue))); + } + + public Builder setCreateBillingCostTransitionsInstant( + ImmutableSortedMap createCostsMap) { checkArgumentNotNull(createCostsMap, "Create billing costs map cannot be null"); checkArgument( createCostsMap.values().stream().allMatch(Money::isPositiveOrZero), "Create billing cost cannot be negative"); getInstance().createBillingCostTransitions = - TimedTransitionProperty.fromValueMap(createCostsMap); + TimedTransitionProperty.fromValueMapInstant(createCostsMap); return this; } @@ -1021,25 +1127,44 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl */ public Builder setRenewBillingCostTransitions( ImmutableSortedMap renewCostsMap) { + return setRenewBillingCostTransitionsInstant( + renewCostsMap.entrySet().stream() + .collect( + ImmutableSortedMap.toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue))); + } + + public Builder setRenewBillingCostTransitionsInstant( + ImmutableSortedMap renewCostsMap) { checkArgumentNotNull(renewCostsMap, "Renew billing costs map cannot be null"); checkArgument( renewCostsMap.values().stream().allMatch(Money::isPositiveOrZero), "Renew billing cost cannot be negative"); getInstance().renewBillingCostTransitions = - TimedTransitionProperty.fromValueMap(renewCostsMap); + TimedTransitionProperty.fromValueMapInstant(renewCostsMap); return this; } /** Sets the EAP fee schedule for the TLD. */ public Builder setEapFeeSchedule(ImmutableSortedMap eapFeeSchedule) { - checkArgumentNotNull(eapFeeSchedule, "EAP schedule map cannot be null"); + return setEapFeeScheduleInstant( + eapFeeSchedule.entrySet().stream() + .collect( + ImmutableSortedMap.toImmutableSortedMap( + Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue))); + } + + /** Sets the EAP fee schedule for the TLD. */ + public Builder setEapFeeScheduleInstant(ImmutableSortedMap eapFeeSchedule) { + checkArgumentNotNull(eapFeeSchedule, "EAP fee schedule cannot be null"); checkArgument( eapFeeSchedule.values().stream().allMatch(Money::isPositiveOrZero), "EAP fee cannot be negative"); - getInstance().eapFeeSchedule = TimedTransitionProperty.fromValueMap(eapFeeSchedule); + getInstance().eapFeeSchedule = TimedTransitionProperty.fromValueMapInstant(eapFeeSchedule); return this; } + public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d]{1,8}$"); public Builder setRoidSuffix(String roidSuffix) { @@ -1071,6 +1196,10 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl } public Builder setClaimsPeriodEnd(DateTime claimsPeriodEnd) { + return setClaimsPeriodEndInstant(toInstant(claimsPeriodEnd)); + } + + public Builder setClaimsPeriodEndInstant(Instant claimsPeriodEnd) { getInstance().claimsPeriodEnd = checkArgumentNotNull(claimsPeriodEnd); return this; } @@ -1115,6 +1244,12 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl } public Builder setBsaEnrollStartTime(Optional enrollTime) { + // TODO(b/309175133): forbid if enrolled with BSA + getInstance().bsaEnrollStartTime = enrollTime.map(DateTimeUtils::toInstant).orElse(null); + return this; + } + + public Builder setBsaEnrollStartTimeInstant(Optional enrollTime) { // TODO(b/309175133): forbid if enrolled with BSA getInstance().bsaEnrollStartTime = enrollTime.orElse(null); return this; @@ -1153,13 +1288,15 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl Predicate currencyCheck = (Money money) -> money.getCurrencyUnit().equals(instance.currency); checkArgument( - instance.getRenewBillingCostTransitions().values().stream().allMatch(currencyCheck), + instance.getRenewBillingCostTransitionsInstant().values().stream() + .allMatch(currencyCheck), "Renew cost must be in the TLD's currency"); checkArgument( - instance.getCreateBillingCostTransitions().values().stream().allMatch(currencyCheck), + instance.getCreateBillingCostTransitionsInstant().values().stream() + .allMatch(currencyCheck), "Create cost must be in the TLD's currency"); checkArgument( - instance.eapFeeSchedule.toValueMap().values().stream().allMatch(currencyCheck), + instance.eapFeeSchedule.toValueMapInstant().values().stream().allMatch(currencyCheck), "All EAP fees must be in the TLD's currency"); checkArgumentNotNull( instance.pricingEngineClassName, "All registries must have a configured pricing engine"); diff --git a/core/src/main/java/google/registry/model/tld/Tlds.java b/core/src/main/java/google/registry/model/tld/Tlds.java index 9cd06a67a..ad6a2940c 100644 --- a/core/src/main/java/google/registry/model/tld/Tlds.java +++ b/core/src/main/java/google/registry/model/tld/Tlds.java @@ -25,6 +25,7 @@ import static google.registry.model.CacheUtils.memoizeWithShortExpiration; import static google.registry.model.tld.Tld.isEnrolledWithBsa; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.entriesToImmutableMap; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.base.Joiner; @@ -36,6 +37,7 @@ import com.google.common.net.InternetDomainName; import google.registry.model.tld.Tld.TldType; import google.registry.util.DomainNameUtils; import jakarta.persistence.EntityManager; +import java.time.Instant; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Stream; @@ -154,7 +156,16 @@ public final class Tlds { } /** Returns true if at least one TLD is enrolled {@code now}. */ - public static boolean hasActiveBsaEnrollment(DateTime now) { + public static boolean hasActiveBsaEnrollment(Instant now) { return getTldEntitiesOfType(TldType.REAL).stream().anyMatch(tld -> isEnrolledWithBsa(tld, now)); } + + /** + * @deprecated Use {@link #hasActiveBsaEnrollment(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static boolean hasActiveBsaEnrollment(DateTime now) { + return hasActiveBsaEnrollment(toInstant(now)); + } } diff --git a/core/src/main/java/google/registry/model/tmch/ClaimsList.java b/core/src/main/java/google/registry/model/tmch/ClaimsList.java index 456d12f6c..23808ea54 100644 --- a/core/src/main/java/google/registry/model/tmch/ClaimsList.java +++ b/core/src/main/java/google/registry/model/tmch/ClaimsList.java @@ -19,6 +19,8 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; @@ -38,6 +40,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.persistence.Transient; +import java.time.Instant; import java.util.Map; import java.util.Optional; import org.joda.time.DateTime; @@ -64,7 +67,7 @@ public class ClaimsList extends ImmutableObject { name = "creationTime", column = @Column(name = "creationTimestamp", nullable = false)) }) - CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null); + CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create((Instant) null); /** * When the claims list was last updated. @@ -74,7 +77,7 @@ public class ClaimsList extends ImmutableObject { * the DNL List creation datetime from the rfc. */ @Column(nullable = false) - DateTime tmdbGenerationTime; + Instant tmdbGenerationTime; /** * A map from labels to claims keys. @@ -138,16 +141,33 @@ public class ClaimsList extends ImmutableObject { * * @see DNL List * creation datetime + * @deprecated Use {@link #getTmdbGenerationTimeInstant()} */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getTmdbGenerationTime() { + return toDateTime(tmdbGenerationTime); + } + + /** Returns the time when the external TMDB service generated this revision of the claims list. */ + public Instant getTmdbGenerationTimeInstant() { return tmdbGenerationTime; } /** Returns the creation time of this claims list. */ - public DateTime getCreationTimestamp() { + public Instant getCreationTime() { return creationTimestamp.getTimestamp(); } + /** + * @deprecated Use {@link #getCreationTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getCreationDateTime() { + return toDateTime(creationTimestamp.getTimestamp()); + } + /** * Returns the claim key for a given domain if there is one, empty otherwise. * @@ -214,10 +234,20 @@ public class ClaimsList extends ImmutableObject { } public static ClaimsList create( - DateTime tmdbGenerationTime, ImmutableMap labelsToKeys) { + Instant tmdbGenerationTime, ImmutableMap labelsToKeys) { ClaimsList instance = new ClaimsList(); instance.tmdbGenerationTime = checkNotNull(tmdbGenerationTime); instance.labelsToKeys = checkNotNull(labelsToKeys); return instance; } + + /** + * @deprecated Use {@link #create(Instant, ImmutableMap)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public static ClaimsList create( + DateTime tmdbGenerationTime, ImmutableMap labelsToKeys) { + return create(toInstant(tmdbGenerationTime), labelsToKeys); + } } 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 e1a0a04c1..495164329 100644 --- a/core/src/main/java/google/registry/model/transfer/BaseTransferObject.java +++ b/core/src/main/java/google/registry/model/transfer/BaseTransferObject.java @@ -14,17 +14,20 @@ package google.registry.model.transfer; +import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.DateTimeUtils.toInstant; import google.registry.model.Buildable.GenericBuilder; import google.registry.model.ImmutableObject; import google.registry.model.UnsafeSerializable; +import google.registry.xml.UtcInstantAdapter; import jakarta.persistence.Column; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.MappedSuperclass; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlTransient; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.time.Instant; import org.joda.time.DateTime; @@ -44,7 +47,8 @@ public abstract class BaseTransferObject extends ImmutableObject implements Unsa /** The time that the last transfer was requested. Can be null if never transferred. */ @XmlElement(name = "reDate") - DateTime transferRequestTime; + @XmlJavaTypeAdapter(UtcInstantAdapter.class) + Instant transferRequestTime; /** The losing registrar of the current or last transfer. Can be null if never transferred. */ @XmlElement(name = "acID") @@ -57,8 +61,9 @@ public abstract class BaseTransferObject extends ImmutableObject implements Unsa * this holds the time that the last pending transfer ended. Can be null if never transferred. */ @XmlElement(name = "acDate") + @XmlJavaTypeAdapter(UtcInstantAdapter.class) @Column(name = "transfer_pending_expiration_time") - DateTime pendingTransferExpirationTime; + Instant pendingTransferExpirationTime; public TransferStatus getTransferStatus() { return transferStatus; @@ -68,7 +73,16 @@ public abstract class BaseTransferObject extends ImmutableObject implements Unsa return gainingClientId; } + /** + * @deprecated Use {@link #getTransferRequestTimeInstant()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getTransferRequestTime() { + return toDateTime(transferRequestTime); + } + + public Instant getTransferRequestTimeInstant() { return transferRequestTime; } @@ -76,13 +90,17 @@ public abstract class BaseTransferObject extends ImmutableObject implements Unsa return losingClientId; } + /** + * @deprecated Use {@link #getPendingTransferExpirationTime()} + */ @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getPendingTransferExpirationDateTime() { - return pendingTransferExpirationTime; + return toDateTime(pendingTransferExpirationTime); } public Instant getPendingTransferExpirationTime() { - return toInstant(pendingTransferExpirationTime); + return pendingTransferExpirationTime; } /** Base class for builders of {@link BaseTransferObject} subclasses. */ @@ -110,11 +128,20 @@ public abstract class BaseTransferObject extends ImmutableObject implements Unsa } /** Set the time that the current transfer request was made on this resource. */ - public B setTransferRequestTime(DateTime transferRequestTime) { + public B setTransferRequestTime(Instant transferRequestTime) { getInstance().transferRequestTime = transferRequestTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setTransferRequestTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setTransferRequestTime(DateTime transferRequestTime) { + return setTransferRequestTime(toInstant(transferRequestTime)); + } + /** Set the losing registrar for a pending transfer on this resource. */ public B setLosingRegistrarId(String losingRegistrarId) { getInstance().losingClientId = losingRegistrarId; @@ -122,11 +149,20 @@ public abstract class BaseTransferObject extends ImmutableObject implements Unsa } /** Set the expiration time of the current pending transfer. */ - public B setPendingTransferExpirationTime(DateTime pendingTransferExpirationTime) { + public B setPendingTransferExpirationTime(Instant pendingTransferExpirationTime) { getInstance().pendingTransferExpirationTime = pendingTransferExpirationTime; return thisCastToDerived(); } + /** + * @deprecated Use {@link #setPendingTransferExpirationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public B setPendingTransferExpirationTime(DateTime pendingTransferExpirationTime) { + return setPendingTransferExpirationTime(toInstant(pendingTransferExpirationTime)); + } + @Override public T build() { return super.build(); diff --git a/core/src/main/java/google/registry/model/transfer/DomainTransferData.java b/core/src/main/java/google/registry/model/transfer/DomainTransferData.java index a56c3889d..d1801501d 100644 --- a/core/src/main/java/google/registry/model/transfer/DomainTransferData.java +++ b/core/src/main/java/google/registry/model/transfer/DomainTransferData.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.util.CollectionUtils.isNullOrEmpty; import static google.registry.util.CollectionUtils.nullToEmpty; +import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableList; @@ -113,7 +114,7 @@ public class DomainTransferData extends BaseTransferObject implements Buildable */ // TODO(b/36405140): backfill this field for existing domains to which it should apply. @Column(name = "transfer_registration_expiration_time") - DateTime transferredRegistrationExpirationTime; + Instant transferredRegistrationExpirationTime; @Column(name = "transfer_billing_cancellation_id") @Convert(converter = VKeyConverter_BillingCancellation.class) @@ -171,13 +172,14 @@ public class DomainTransferData extends BaseTransferObject implements Buildable @Nullable @Deprecated + @SuppressWarnings("InlineMeSuggester") public DateTime getTransferredRegistrationExpirationDateTime() { - return transferredRegistrationExpirationTime; + return toDateTime(transferredRegistrationExpirationTime); } @Nullable public Instant getTransferredRegistrationExpirationTime() { - return toInstant(transferredRegistrationExpirationTime); + return transferredRegistrationExpirationTime; } @Nullable @@ -334,11 +336,22 @@ public class DomainTransferData extends BaseTransferObject implements Buildable } public Builder setTransferredRegistrationExpirationTime( - DateTime transferredRegistrationExpirationTime) { + Instant transferredRegistrationExpirationTime) { getInstance().transferredRegistrationExpirationTime = transferredRegistrationExpirationTime; return this; } + /** + * @deprecated Use {@link #setTransferredRegistrationExpirationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setTransferredRegistrationExpirationTime( + DateTime transferredRegistrationExpirationTime) { + return setTransferredRegistrationExpirationTime( + toInstant(transferredRegistrationExpirationTime)); + } + public Builder setServerApproveBillingEvent(VKey serverApproveBillingEvent) { getInstance().serverApproveBillingEvent = serverApproveBillingEvent; return this; diff --git a/core/src/main/java/google/registry/model/transfer/TransferResponse.java b/core/src/main/java/google/registry/model/transfer/TransferResponse.java index c7ee86753..e3a8f46b9 100644 --- a/core/src/main/java/google/registry/model/transfer/TransferResponse.java +++ b/core/src/main/java/google/registry/model/transfer/TransferResponse.java @@ -14,13 +14,19 @@ package google.registry.model.transfer; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; + import google.registry.model.EppResource; import google.registry.model.eppoutput.EppResponse.ResponseData; +import google.registry.xml.UtcInstantAdapter; import jakarta.persistence.Embeddable; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlTransient; import jakarta.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; import org.joda.time.DateTime; /** @@ -58,9 +64,19 @@ public class TransferResponse extends BaseTransferObject implements ResponseData * will only be set on pending or approved transfers, not on cancelled or rejected ones. */ @XmlElement(name = "exDate") - DateTime extendedRegistrationExpirationTime; + @XmlJavaTypeAdapter(UtcInstantAdapter.class) + Instant extendedRegistrationExpirationTime; - public DateTime getExtendedRegistrationExpirationTime() { + /** + * @deprecated Use {@link #getExtendedRegistrationExpirationTime()} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public DateTime getExtendedRegistrationExpirationDateTime() { + return toDateTime(extendedRegistrationExpirationTime); + } + + public Instant getExtendedRegistrationExpirationTime() { return extendedRegistrationExpirationTime; } @@ -74,10 +90,20 @@ public class TransferResponse extends BaseTransferObject implements ResponseData /** Set the registration expiration time that will take effect if this transfer completes. */ public Builder setExtendedRegistrationExpirationTime( - DateTime extendedRegistrationExpirationTime) { + Instant extendedRegistrationExpirationTime) { getInstance().extendedRegistrationExpirationTime = extendedRegistrationExpirationTime; return this; } + + /** + * @deprecated Use {@link #setExtendedRegistrationExpirationTime(Instant)} + */ + @Deprecated + @SuppressWarnings("InlineMeSuggester") + public Builder setExtendedRegistrationExpirationTime( + DateTime extendedRegistrationExpirationTime) { + return setExtendedRegistrationExpirationTime(toInstant(extendedRegistrationExpirationTime)); + } } } diff --git a/core/src/main/java/google/registry/model/transfer/package-info.java b/core/src/main/java/google/registry/model/transfer/package-info.java index 55313b7a8..620f809fa 100644 --- a/core/src/main/java/google/registry/model/transfer/package-info.java +++ b/core/src/main/java/google/registry/model/transfer/package-info.java @@ -17,13 +17,18 @@ xmlns = @XmlNs(prefix = "", namespaceURI = "urn:ietf:params:xml:ns:epp-1.0"), elementFormDefault = XmlNsForm.QUALIFIED) @XmlAccessorType(XmlAccessType.FIELD) -@XmlJavaTypeAdapter(UtcDateTimeAdapter.class) +@XmlJavaTypeAdapters({ + @XmlJavaTypeAdapter(UtcDateTimeAdapter.class), + @XmlJavaTypeAdapter(UtcInstantAdapter.class) +}) package google.registry.model.transfer; import google.registry.xml.UtcDateTimeAdapter; +import google.registry.xml.UtcInstantAdapter; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlNs; import jakarta.xml.bind.annotation.XmlNsForm; import jakarta.xml.bind.annotation.XmlSchema; import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; diff --git a/core/src/main/java/google/registry/persistence/converter/TimedTransitionBaseUserType.java b/core/src/main/java/google/registry/persistence/converter/TimedTransitionBaseUserType.java index 7fcddbf93..3cbbcc740 100644 --- a/core/src/main/java/google/registry/persistence/converter/TimedTransitionBaseUserType.java +++ b/core/src/main/java/google/registry/persistence/converter/TimedTransitionBaseUserType.java @@ -16,13 +16,15 @@ package google.registry.persistence.converter; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; +import static google.registry.util.DateTimeUtils.parseInstant; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; import google.registry.model.common.TimedTransitionProperty; import java.io.Serializable; +import java.time.Instant; import java.util.Map; -import org.joda.time.DateTime; /** * Base Hibernate custom type for {@link TimedTransitionProperty}. @@ -44,19 +46,21 @@ public abstract class TimedTransitionBaseUserType @Override Map toStringMap(TimedTransitionProperty map) { - return map.toValueMap().entrySet().stream() - .collect(toImmutableMap(e -> e.getKey().toString(), e -> valueToString(e.getValue()))); + return map.toValueMapInstant().entrySet().stream() + .collect( + toImmutableMap( + e -> ISO_8601_FORMATTER.format(e.getKey()), e -> valueToString(e.getValue()))); } @Override TimedTransitionProperty toEntity(Map map) { - ImmutableSortedMap valueMap = + ImmutableSortedMap valueMap = map.entrySet().stream() .collect( toImmutableSortedMap( Ordering.natural(), - e -> DateTime.parse(e.getKey()), + e -> parseInstant(e.getKey()), e -> stringToValue(e.getValue()))); - return TimedTransitionProperty.fromValueMap(valueMap); + return TimedTransitionProperty.fromValueMapInstant(valueMap); } } diff --git a/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java b/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java index 95994b268..e2351ec82 100644 --- a/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java +++ b/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java @@ -16,6 +16,7 @@ package google.registry.rdap; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.base.Joiner; @@ -33,6 +34,7 @@ import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.time.Instant; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -338,6 +340,12 @@ abstract class AbstractJsonableObject implements Jsonable { // According to RFC3339, we should use ISO8601, which is what DateTime.toString does! return new JsonPrimitive(object.toString()); } + if (object instanceof Instant) { + // According to RFC 9083 section 3, the syntax of dates and times is defined in RFC3339. + // + // According to RFC3339, we should use ISO8601, so we use ISO_8601_FORMATTER. + return new JsonPrimitive(ISO_8601_FORMATTER.format((Instant) object)); + } if (object == null) { return JsonNull.INSTANCE; } diff --git a/core/src/main/java/google/registry/rdap/RdapActionBase.java b/core/src/main/java/google/registry/rdap/RdapActionBase.java index 94804bf0a..f9335092c 100644 --- a/core/src/main/java/google/registry/rdap/RdapActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapActionBase.java @@ -17,7 +17,6 @@ package google.registry.rdap; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; import static google.registry.request.Actions.getPathForAction; -import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.DomainNameUtils.canonicalizeHostname; import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; @@ -52,8 +51,8 @@ import google.registry.util.Clock; import jakarta.inject.Inject; import java.net.URI; import java.net.URISyntaxException; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; /** * Base RDAP action for all requests. @@ -242,7 +241,7 @@ public abstract class RdapActionBase implements Runnable { * is authorized to do so. */ boolean isAuthorized(EppResource eppResource) { - return toInstant(getRequestTime()).isBefore(eppResource.getDeletionTime()) + return getRequestTime().isBefore(eppResource.getDeletionTime()) || (shouldIncludeDeleted() && rdapAuthorization.isAuthorizedForRegistrar( eppResource.getPersistedCurrentSponsorRegistrarId())); @@ -268,8 +267,8 @@ public abstract class RdapActionBase implements Runnable { return name; } - /** Returns the DateTime this request took place. */ - DateTime getRequestTime() { + /** Returns the Instant this request took place. */ + Instant getRequestTime() { return rdapJsonFormatter.getRequestTime(); } diff --git a/core/src/main/java/google/registry/rdap/RdapDataStructures.java b/core/src/main/java/google/registry/rdap/RdapDataStructures.java index fe8ca9bb5..f1445a452 100644 --- a/core/src/main/java/google/registry/rdap/RdapDataStructures.java +++ b/core/src/main/java/google/registry/rdap/RdapDataStructures.java @@ -19,8 +19,8 @@ import com.google.common.collect.ImmutableList; import com.google.gson.JsonArray; import com.google.gson.JsonPrimitive; import google.registry.rdap.AbstractJsonableObject.RestrictJsonNames; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; /** Data Structures defined in RFC 9083 section 4. */ final class RdapDataStructures { @@ -238,13 +238,18 @@ final class RdapDataStructures { */ private abstract static class EventBase extends AbstractJsonableObject { @JsonableElement abstract EventAction eventAction(); - @JsonableElement abstract DateTime eventDate(); + + @JsonableElement + abstract Instant eventDate(); + @JsonableElement abstract ImmutableList links(); abstract static class Builder> { abstract B setEventAction(EventAction eventAction); - abstract B setEventDate(DateTime eventDate); + + abstract B setEventDate(Instant eventDate); + abstract ImmutableList.Builder linksBuilder(); @SuppressWarnings("unchecked") diff --git a/core/src/main/java/google/registry/rdap/RdapDomainAction.java b/core/src/main/java/google/registry/rdap/RdapDomainAction.java index e472de198..1ddc74679 100644 --- a/core/src/main/java/google/registry/rdap/RdapDomainAction.java +++ b/core/src/main/java/google/registry/rdap/RdapDomainAction.java @@ -17,7 +17,7 @@ package google.registry.rdap; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; import com.google.common.net.InternetDomainName; import google.registry.flows.EppException; @@ -70,7 +70,7 @@ public class RdapDomainAction extends RdapActionBase { ForeignKeyUtils.loadResourceByCache( Domain.class, pathSearchString, - shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime()); + shouldIncludeDeleted() ? START_INSTANT : getRequestTime()); if (domain.isEmpty() || !isAuthorized(domain.get())) { handlePossibleBsaBlock(domainName); // RFC7480 5.3 - if the server wishes to respond that it doesn't have data satisfying the diff --git a/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java b/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java index 666d038b0..f79878f29 100644 --- a/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java @@ -18,8 +18,8 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.START_INSTANT; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; @@ -52,12 +52,12 @@ import jakarta.inject.Inject; import jakarta.persistence.Query; import jakarta.persistence.criteria.CriteriaBuilder; import java.net.InetAddress; +import java.time.Instant; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.hibernate.Hibernate; -import org.joda.time.DateTime; /** * RDAP action for domain search requests. @@ -332,7 +332,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { /** Assembles a list of {@link Host} keys by name when the pattern has no wildcard. */ private ImmutableList> getNameserverRefsByLdhNameWithoutWildcard( final RdapSearchPattern partialStringQuery) { - DateTime timeToQuery = shouldIncludeDeleted() ? START_OF_TIME : getRequestTime(); + Instant timeToQuery = shouldIncludeDeleted() ? START_INSTANT : getRequestTime(); // If we need to check the sponsoring registrar, we need to load the resource rather than just // the key. Optional desiredRegistrar = getDesiredRegistrar(); @@ -354,7 +354,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { /** Assembles a list of {@link Host} keys by name using a superordinate domain suffix. */ private ImmutableList> getNameserverRefsByLdhNameWithSuffix( RdapSearchPattern partialStringQuery) { - DateTime timeToQuery = shouldIncludeDeleted() ? START_OF_TIME : getRequestTime(); + Instant timeToQuery = shouldIncludeDeleted() ? START_INSTANT : getRequestTime(); // The suffix must be a domain that we manage. That way, we can look up the domain and search // through the subordinate hosts. This is more efficient, and lets us permit wildcard searches // with no initial string. @@ -410,17 +410,17 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { private DomainSearchResponse searchByNameserverIp(final InetAddress inetAddress) { Optional desiredRegistrar = getDesiredRegistrar(); ImmutableSet> hostKeys; - // Hibernate does not allow us to query @Converted array fields directly, either - // in the CriteriaQuery or the raw text format. However, Postgres does -- so we - // use native queries to find hosts where any of the inetAddresses match. - StringBuilder queryBuilder = - new StringBuilder( - "SELECT h.repo_id FROM \"Host\" h WHERE :address = ANY(h.inet_addresses) AND " - + "h.deletion_time = CAST(:endOfTime AS timestamptz)"); - ImmutableMap.Builder parameters = - new ImmutableMap.Builder() - .put("address", InetAddresses.toAddrString(inetAddress)) - .put("endOfTime", END_OF_TIME.toString()); + // Hibernate does not allow us to query @Converted array fields directly, either + // in the CriteriaQuery or the raw text format. However, Postgres does -- so we + // use native queries to find hosts where any of the inetAddresses match. + StringBuilder queryBuilder = + new StringBuilder( + "SELECT h.repo_id FROM \"Host\" h WHERE :address = ANY(h.inet_addresses) AND " + + "h.deletion_time = :endOfTime"); + ImmutableMap.Builder parameters = + new ImmutableMap.Builder() + .put("address", InetAddresses.toAddrString(inetAddress)) + .put("endOfTime", END_INSTANT); if (desiredRegistrar.isPresent()) { queryBuilder.append(" AND h.current_sponsor_registrar_id = :desiredRegistrar"); parameters.put("desiredRegistrar", desiredRegistrar.get()); diff --git a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java index c9446bb47..ab0d1335e 100644 --- a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java +++ b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java @@ -20,8 +20,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.model.EppResourceUtils.isLinked; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; -import static google.registry.util.DateTimeUtils.toDateTime; -import static google.registry.util.DateTimeUtils.toInstant; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; @@ -49,6 +47,7 @@ import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarPoc; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntryDao; +import google.registry.model.transfer.TransferStatus; import google.registry.persistence.VKey; import google.registry.rdap.RdapDataStructures.Event; import google.registry.rdap.RdapDataStructures.EventAction; @@ -72,12 +71,12 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; import java.nio.file.Paths; +import java.time.Instant; import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.joda.time.DateTime; /** * Helper class to create RDAP JSON objects for various registry entities and objects. @@ -96,7 +95,7 @@ public class RdapJsonFormatter { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @VisibleForTesting - record HistoryTimeAndRegistrar(DateTime modificationTime, String registrarId) {} + record HistoryTimeAndRegistrar(Instant modificationTime, String registrarId) {} private static final LoadingCache> DOMAIN_HISTORIES_BY_REPO_ID = @@ -105,7 +104,7 @@ public class RdapJsonFormatter { .maximumSize(RegistryConfig.getEppResourceMaxCachedEntries() * 4L) .build(repoId -> getLastHistoryByType(repoId, Domain.class)); - private DateTime requestTime = null; + private Instant requestTime = null; @Inject @Config("rdapTos") @@ -320,11 +319,11 @@ public class RdapJsonFormatter { .setEventAction(EventAction.REGISTRATION) .setEventActor( Optional.ofNullable(domain.getCreationRegistrarId()).orElse("(none)")) - .setEventDate(domain.getCreationTime()) + .setEventDate(domain.getCreationTimeInstant()) .build(), Event.builder() .setEventAction(EventAction.EXPIRATION) - .setEventDate(domain.getRegistrationExpirationDateTime()) + .setEventDate(domain.getRegistrationExpirationTime()) .build(), // RDAP response profile section 1.5: // The topmost object in the RDAP response MUST contain an event of "eventAction" type @@ -340,14 +339,14 @@ public class RdapJsonFormatter { // (i.e. without updating lastEppUpdateTime), that can only happen for domains that have already // been modified in some way. As a result, we can ignore those cases here. if (domain.getLastEppUpdateTime() != null - && domain.getLastEppUpdateTime().isAfter(toInstant(domain.getCreationTime()))) { + && domain.getLastEppUpdateTime().isAfter(domain.getCreationTimeInstant())) { // Creates an RDAP event object as defined by RFC 9083 builder .eventsBuilder() .add( Event.builder() .setEventAction(EventAction.LAST_CHANGED) - .setEventDate(toDateTime(domain.getLastEppUpdateTime())) + .setEventDate(domain.getLastEppUpdateTime()) .build()); } // RDAP Response Profile section 2.3.2 discusses optional events. We add some of those @@ -385,7 +384,8 @@ public class RdapJsonFormatter { makeStatusValueList( allStatusValues, false, // isRedacted - domain.getDeletionDateTime().isBefore(getRequestTime())); + domain.getDeletionTime().isBefore(getRequestTime())); + builder.statusBuilder().addAll(status); if (status.isEmpty()) { logger.atWarning().log( @@ -452,26 +452,23 @@ public class RdapJsonFormatter { if (outputDataType == OutputDataType.FULL) { ImmutableSet.Builder statuses = new ImmutableSet.Builder<>(); statuses.addAll(host.getStatusValues()); + VKey superordinateDomain = host.getSuperordinateDomain(); + if (superordinateDomain != null) { + Domain domain = replicaTm().reTransact(() -> replicaTm().loadByKey(superordinateDomain)); + if (TransferStatus.PENDING.equals(domain.getTransferData().getTransferStatus())) { + statuses.add(StatusValue.PENDING_TRANSFER); + } + } if (isLinked(host.createVKey(), getRequestTime())) { statuses.add(StatusValue.LINKED); } - if (host.isSubordinate() - && replicaTm() - .transact( - () -> - EppResource.loadByCache(host.getSuperordinateDomain()) - .cloneProjectedAtTime(getRequestTime()) - .getStatusValues() - .contains(StatusValue.PENDING_TRANSFER))) { - statuses.add(StatusValue.PENDING_TRANSFER); - } builder .statusBuilder() .addAll( makeStatusValueList( statuses.build(), false, // isRedacted - host.getDeletionDateTime().isBefore(getRequestTime()))); + host.getDeletionTime().isBefore(getRequestTime()))); } // For query responses - we MUST have all the ip addresses: RDAP Response Profile 4.2. @@ -762,7 +759,7 @@ public class RdapJsonFormatter { lastEntryOfType.put( rdapEventAction, new HistoryTimeAndRegistrar( - historyEntry.getModificationTime(), + historyEntry.getModificationTimeInstant(), historyEntry.getRegistrarId())); } })); @@ -921,7 +918,7 @@ public class RdapJsonFormatter { } /** - * Returns the DateTime this request took place. + * Returns the Instant this request took place. * *

The RDAP reply is large with a lot of different object in them. We want to make sure that * all these objects are projected to the same "now". @@ -935,9 +932,9 @@ public class RdapJsonFormatter { *

We would like even more to just inject it in RequestModule and use it in many places in our * codebase that just need a general "now" of the request, but that's a lot of work. */ - DateTime getRequestTime() { + Instant getRequestTime() { if (requestTime == null) { - requestTime = clock.nowUtc(); + requestTime = clock.now(); } return requestTime; } diff --git a/core/src/main/java/google/registry/rdap/RdapNameserverAction.java b/core/src/main/java/google/registry/rdap/RdapNameserverAction.java index 11e243dbe..4837e765c 100644 --- a/core/src/main/java/google/registry/rdap/RdapNameserverAction.java +++ b/core/src/main/java/google/registry/rdap/RdapNameserverAction.java @@ -17,7 +17,7 @@ package google.registry.rdap; import static google.registry.flows.host.HostFlowUtils.validateHostName; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; import google.registry.flows.EppException; import google.registry.model.ForeignKeyUtils; @@ -65,7 +65,7 @@ public class RdapNameserverAction extends RdapActionBase { ForeignKeyUtils.loadResourceByCache( Host.class, pathSearchString, - shouldIncludeDeleted() ? START_OF_TIME : getRequestTime()); + shouldIncludeDeleted() ? START_INSTANT : getRequestTime()); if (host.isEmpty() || !isAuthorized(host.get())) { // RFC7480 5.3 - if the server wishes to respond that it doesn't have data satisfying the // query, it MUST reply with 404 response code. diff --git a/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java b/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java index 4bf25cbd5..ba0190d9a 100644 --- a/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -17,7 +17,7 @@ package google.registry.rdap; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; @@ -246,12 +246,12 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { // find hosts where any of the inetAddresses match. StringBuilder queryBuilder = new StringBuilder("SELECT * FROM \"Host\" WHERE :address = ANY(inet_addresses)"); - ImmutableMap.Builder parameters = - new ImmutableMap.Builder() - .put("address", InetAddresses.toAddrString(inetAddress)); + ImmutableMap.Builder parameters = + new ImmutableMap.Builder() + .put("address", InetAddresses.toAddrString(inetAddress)); if (getDeletedItemHandling().equals(DeletedItemHandling.EXCLUDE)) { - queryBuilder.append(" AND deletion_time = CAST(:endOfTime AS timestamptz)"); - parameters.put("endOfTime", END_OF_TIME.toString()); + queryBuilder.append(" AND deletion_time = :endOfTime"); + parameters.put("endOfTime", END_INSTANT); } if (cursorString.isPresent()) { // cursorString here must be the repo ID diff --git a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java index 6d05ee9cb..bf492a85b 100644 --- a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java @@ -15,7 +15,7 @@ package google.registry.rdap; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -396,7 +396,7 @@ public abstract class RdapSearchActionBase extends RdapActionBase { builder.where( "deletionTime", replicaTm().getEntityManager().getCriteriaBuilder()::equal, - END_OF_TIME); + END_INSTANT); } return builder; } diff --git a/core/src/main/java/google/registry/rde/HostToXjcConverter.java b/core/src/main/java/google/registry/rde/HostToXjcConverter.java index f822871aa..c674e9358 100644 --- a/core/src/main/java/google/registry/rde/HostToXjcConverter.java +++ b/core/src/main/java/google/registry/rde/HostToXjcConverter.java @@ -15,6 +15,7 @@ package google.registry.rde; import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.util.DateTimeUtils.toDateTime; import com.google.common.net.InetAddresses; import google.registry.model.domain.Domain; @@ -51,7 +52,7 @@ final class HostToXjcConverter { convertHostCommon( model, superordinateDomain.getCurrentSponsorRegistrarId(), - model.computeLastTransferTime(superordinateDomain)); + toDateTime(model.computeLastTransferTime(superordinateDomain))); if (superordinateDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) { bean.getStatuses().add(convertStatusValue(StatusValue.PENDING_TRANSFER)); } diff --git a/core/src/main/java/google/registry/rde/RegistrarToXjcConverter.java b/core/src/main/java/google/registry/rde/RegistrarToXjcConverter.java index f19104e10..092841bf6 100644 --- a/core/src/main/java/google/registry/rde/RegistrarToXjcConverter.java +++ b/core/src/main/java/google/registry/rde/RegistrarToXjcConverter.java @@ -16,6 +16,7 @@ package google.registry.rde; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkState; +import static google.registry.util.DateTimeUtils.toDateTime; import com.google.common.collect.ImmutableMap; import google.registry.model.registrar.Registrar; @@ -139,7 +140,7 @@ final class RegistrarToXjcConverter { // o A element that contains the date and time of registrar- // object creation. - bean.setCrDate(model.getCreationTime()); + bean.setCrDate(toDateTime(model.getCreationTime())); // o An OPTIONAL element that contains the date and time of // the most recent RDE registrar-object modification. This element diff --git a/core/src/main/java/google/registry/request/RequestParameters.java b/core/src/main/java/google/registry/request/RequestParameters.java index dc47df204..6cd6a034b 100644 --- a/core/src/main/java/google/registry/request/RequestParameters.java +++ b/core/src/main/java/google/registry/request/RequestParameters.java @@ -19,12 +19,15 @@ import static com.google.common.base.Strings.emptyToNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static google.registry.util.DateTimeUtils.parseInstant; import com.google.common.base.Ascii; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import google.registry.request.HttpException.BadRequestException; import jakarta.servlet.http.HttpServletRequest; +import java.time.Instant; +import java.time.format.DateTimeParseException; import java.util.Optional; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -253,6 +256,40 @@ public final class RequestParameters { return req.getParameterMap().containsKey(name) && !equalsFalse(req.getParameter(name)); } + /** + * Returns first request parameter associated with {@code name} parsed as an ISO 8601 timestamp, e.g. {@code 1984-12-18TZ}, {@code + * 2000-01-01T16:20:00Z}. + * + * @throws BadRequestException if request parameter named {@code name} is absent, empty, or could + * not be parsed as an ISO 8601 timestamp + */ + public static Instant extractRequiredInstantParameter(HttpServletRequest req, String name) { + String stringValue = extractRequiredParameter(req, name); + try { + return parseInstant(stringValue); + } catch (DateTimeParseException | IllegalArgumentException e) { + throw new BadRequestException("Bad ISO 8601 timestamp: " + name); + } + } + + /** + * Returns first request parameter associated with {@code name} parsed as an ISO 8601 timestamp, e.g. {@code 1984-12-18TZ}, {@code + * 2000-01-01T16:20:00Z}. + * + * @throws BadRequestException if request parameter is present but not a valid {@link Instant}. + */ + public static Optional extractOptionalInstantParameter( + HttpServletRequest req, String name) { + String stringParam = req.getParameter(name); + try { + return isNullOrEmpty(stringParam) ? Optional.empty() : Optional.of(parseInstant(stringParam)); + } catch (DateTimeParseException | IllegalArgumentException e) { + throw new BadRequestException("Bad ISO 8601 timestamp: " + name); + } + } + /** * Returns first request parameter associated with {@code name} parsed as an ISO 8601 timestamp, e.g. {@code 1984-12-18TZ}, {@code diff --git a/core/src/main/java/google/registry/tools/ConfigureFeatureFlagCommand.java b/core/src/main/java/google/registry/tools/ConfigureFeatureFlagCommand.java index e0a98a5df..9c04d5c36 100644 --- a/core/src/main/java/google/registry/tools/ConfigureFeatureFlagCommand.java +++ b/core/src/main/java/google/registry/tools/ConfigureFeatureFlagCommand.java @@ -21,9 +21,9 @@ import google.registry.model.common.FeatureFlag; import google.registry.model.common.FeatureFlag.FeatureName; import google.registry.model.common.FeatureFlag.FeatureStatus; import google.registry.tools.params.TransitionListParameter.FeatureStatusTransitions; +import java.time.Instant; import java.util.List; import java.util.Optional; -import org.joda.time.DateTime; /** Command for creating and updating {@link FeatureFlag} objects. */ @Parameters(separators = " =", commandDescription = "Create or update a feature flag.") @@ -41,14 +41,17 @@ public class ConfigureFeatureFlagCommand extends MutatingCommand { + " form

Delegates to {@link DateTimeParameter} for parsing, then converts to {@link Instant}. + */ + @Override + public Instant convert(String value) { + return toInstant(DATE_TIME_CONVERTER.convert(value)); + } +} diff --git a/core/src/main/java/google/registry/tools/params/TransitionListParameter.java b/core/src/main/java/google/registry/tools/params/TransitionListParameter.java index 305618c43..7941af553 100644 --- a/core/src/main/java/google/registry/tools/params/TransitionListParameter.java +++ b/core/src/main/java/google/registry/tools/params/TransitionListParameter.java @@ -22,14 +22,14 @@ import com.google.common.collect.Ordering; import google.registry.model.common.FeatureFlag.FeatureStatus; import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.tld.Tld.TldState; +import java.time.Instant; import org.joda.money.Money; -import org.joda.time.DateTime; /** Combined converter and validator class for transition list JCommander argument strings. */ // TODO(b/19031334): Investigate making this complex generic type work with the factory. -public abstract class TransitionListParameter extends KeyValueMapParameter { +public abstract class TransitionListParameter extends KeyValueMapParameter { - private static final DateTimeParameter DATE_TIME_CONVERTER = new DateTimeParameter(); + private static final InstantParameter DATE_TIME_CONVERTER = new InstantParameter(); public TransitionListParameter() { // This is not sentence-capitalized like most exception messages because it is appended to the @@ -38,12 +38,12 @@ public abstract class TransitionListParameter extends KeyValueMapParameter processMap(ImmutableMap map) { + protected final ImmutableSortedMap processMap(ImmutableMap map) { checkArgument(Ordering.natural().isOrdered(map.keySet()), "Transition times out of order"); return ImmutableSortedMap.copyOf(map); } diff --git a/core/src/main/java/google/registry/tools/server/GenerateZoneFilesAction.java b/core/src/main/java/google/registry/tools/server/GenerateZoneFilesAction.java index 3709ad4dc..6db1ec4c6 100644 --- a/core/src/main/java/google/registry/tools/server/GenerateZoneFilesAction.java +++ b/core/src/main/java/google/registry/tools/server/GenerateZoneFilesAction.java @@ -45,12 +45,12 @@ import java.io.PrintWriter; import java.io.Writer; import java.net.Inet4Address; import java.net.InetAddress; +import java.time.Instant; import java.util.List; import java.util.Map; import org.hibernate.CacheMode; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; -import org.joda.time.DateTime; import org.joda.time.Duration; /** @@ -117,15 +117,16 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA public Map handleJsonRequest(Map json) { @SuppressWarnings("unchecked") ImmutableSet tlds = ImmutableSet.copyOf((List) json.get("tlds")); - final DateTime exportTime = DateTime.parse(json.get("exportTime").toString()); + final Instant exportTime = Instant.parse(json.get("exportTime").toString()); // We disallow exporting within the past 2 minutes because there might be outstanding writes. // We can only reliably call loadAtPointInTime at times that are UTC midnight and > // databaseRetention ago in the past. - DateTime now = clock.nowUtc(); - if (exportTime.isAfter(now.minusMinutes(2))) { + Instant now = clock.now(); + if (exportTime.isAfter(now.minus(java.time.Duration.ofMinutes(2)))) { throw new BadRequestException("Invalid export time: must be > 2 minutes ago"); } - if (exportTime.isBefore(now.minus(databaseRetention))) { + if (exportTime.isBefore( + now.minus(java.time.Duration.ofMillis(databaseRetention.getMillis())))) { throw new BadRequestException( String.format( "Invalid export time: must be < %d days ago", databaseRetention.getStandardDays())); @@ -142,7 +143,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA "filenames", filenames); } - private void generateForTld(String tld, DateTime exportTime) { + private void generateForTld(String tld, Instant exportTime) { ImmutableList stanzas = tm().transact(() -> getStanzasForTld(tld, exportTime)); BlobId outputBlobId = BlobId.of(bucket, String.format(FILENAME_FORMAT, tld, exportTime)); try (OutputStream gcsOutput = gcsUtils.openOutputStream(outputBlobId); @@ -156,7 +157,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA } } - private ImmutableList getStanzasForTld(String tld, DateTime exportTime) { + private ImmutableList getStanzasForTld(String tld, Instant exportTime) { ImmutableList.Builder result = new ImmutableList.Builder<>(); ScrollableResults scrollableResults = tm().query("FROM Domain WHERE tld = :tld AND deletionTime > :exportTime", Domain.class) @@ -177,7 +178,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA } private void populateStanzasForDomain( - Domain domain, DateTime exportTime, ImmutableList.Builder result) { + Domain domain, Instant exportTime, ImmutableList.Builder result) { domain = loadAtPointInTime(domain, exportTime); // A null means the domain was deleted (or not created) at this time. if (domain == null || !domain.shouldPublishToDns()) { @@ -191,7 +192,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA } private void populateStanzasForSubordinateHosts( - Domain domain, DateTime exportTime, ImmutableList.Builder result) { + Domain domain, Instant exportTime, ImmutableList.Builder result) { ImmutableSet subordinateHosts = domain.getSubordinateHosts(); if (!subordinateHosts.isEmpty()) { for (Host unprojectedHost : tm().loadByKeys(domain.getNameservers()).values()) { @@ -227,7 +228,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA * } * */ - private String domainStanza(Domain domain, DateTime exportTime) { + private String domainStanza(Domain domain, Instant exportTime) { StringBuilder result = new StringBuilder(); String domainLabel = stripTld(domain.getDomainName(), domain.getTld()); Tld tld = Tld.get(domain.getTld()); diff --git a/core/src/main/java/google/registry/tools/server/RefreshDnsForAllDomainsAction.java b/core/src/main/java/google/registry/tools/server/RefreshDnsForAllDomainsAction.java index ce1734955..1534c7d42 100644 --- a/core/src/main/java/google/registry/tools/server/RefreshDnsForAllDomainsAction.java +++ b/core/src/main/java/google/registry/tools/server/RefreshDnsForAllDomainsAction.java @@ -24,6 +24,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.request.RequestParameters.PARAM_BATCH_SIZE; import static google.registry.request.RequestParameters.PARAM_TLDS; import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.toInstant; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -133,7 +134,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable { + " :activeOrDeletedSince", Long.class) .setParameter("tlds", tlds) - .setParameter("activeOrDeletedSince", activeOrDeletedSince) + .setParameter("activeOrDeletedSince", toInstant(activeOrDeletedSince)) .getSingleResult(); Duration smear = Duration.standardSeconds(Math.max(activeDomains / refreshQps, 1)); logger.atInfo().log("Smearing %d domain DNS refresh tasks across %s.", activeDomains, smear); @@ -149,7 +150,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable { TypedQuery query = tm().query(sql, String.class) .setParameter("tlds", tlds) - .setParameter("activeOrDeletedSince", activeOrDeletedSince); + .setParameter("activeOrDeletedSince", toInstant(activeOrDeletedSince)); lastInPreviousBatch.ifPresent(l -> query.setParameter("lastInPreviousBatch", l)); return query.setMaxResults(batchSize).getResultStream().collect(toImmutableList()); } diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java index 562db6d45..6f74427cc 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleDomainListAction.java @@ -17,6 +17,7 @@ package google.registry.ui.server.console; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.model.console.ConsolePermission.DOWNLOAD_DOMAINS; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toInstant; import static jakarta.servlet.http.HttpServletResponse.SC_OK; import com.google.common.annotations.VisibleForTesting; @@ -101,13 +102,13 @@ public class ConsoleDomainListAction extends ConsoleApiAction { createCountQuery() .setParameter("registrarId", registrarId) .setParameter("createdBeforeTime", checkpointTimestamp) - .setParameter("deletedAfterTime", checkpoint) + .setParameter("deletedAfterTime", toInstant(checkpoint)) .getSingleResult()); List domains = createDomainQuery() .setParameter("registrarId", registrarId) .setParameter("createdBeforeTime", checkpointTimestamp) - .setParameter("deletedAfterTime", checkpoint) + .setParameter("deletedAfterTime", toInstant(checkpoint)) .setFirstResult(numResultsToSkip) .setMaxResults(resultsPerPage) .getResultList(); diff --git a/core/src/main/java/google/registry/ui/server/console/PasswordResetVerifyAction.java b/core/src/main/java/google/registry/ui/server/console/PasswordResetVerifyAction.java index 8800756e5..720a71dc6 100644 --- a/core/src/main/java/google/registry/ui/server/console/PasswordResetVerifyAction.java +++ b/core/src/main/java/google/registry/ui/server/console/PasswordResetVerifyAction.java @@ -33,8 +33,8 @@ import google.registry.request.Parameter; import google.registry.request.auth.Auth; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletResponse; +import java.time.Duration; import java.util.Optional; -import org.joda.time.Duration; @Action( service = Action.Service.CONSOLE, @@ -122,10 +122,7 @@ public class PasswordResetVerifyAction extends ConsoleApiAction { case REGISTRY_LOCK -> ConsolePermission.REGISTRY_LOCK; }; checkPermission(user, request.getRegistrarId(), requiredVerifyPermission); - if (request - .getRequestTime() - .plus(Duration.standardHours(1)) - .isBefore(tm().getTransactionTime())) { + if (request.getRequestTime().plus(Duration.ofHours(1)).isBefore(tm().getTxTime())) { throw createVerificationCodeException(); } return request; diff --git a/core/src/main/java/google/registry/xml/UtcDateTimeAdapter.java b/core/src/main/java/google/registry/xml/UtcDateTimeAdapter.java index 0c1bd4123..d1da1622c 100644 --- a/core/src/main/java/google/registry/xml/UtcDateTimeAdapter.java +++ b/core/src/main/java/google/registry/xml/UtcDateTimeAdapter.java @@ -40,10 +40,7 @@ public class UtcDateTimeAdapter extends XmlAdapter { /** Same as {@link #marshal(DateTime)}, but in a convenient static format. */ public static String getFormattedString(@Nullable DateTime timestamp) { - if (timestamp == null) { - return ""; - } - return MARSHAL_FORMAT.print(timestamp.toDateTime(UTC)); + return (timestamp == null) ? "" : MARSHAL_FORMAT.print(timestamp.toDateTime(UTC)); } /** diff --git a/core/src/main/java/google/registry/xml/UtcInstantAdapter.java b/core/src/main/java/google/registry/xml/UtcInstantAdapter.java new file mode 100644 index 000000000..8a7b57675 --- /dev/null +++ b/core/src/main/java/google/registry/xml/UtcInstantAdapter.java @@ -0,0 +1,68 @@ +// Copyright 2026 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.xml; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static google.registry.util.DateTimeUtils.parseInstant; + +import jakarta.xml.bind.annotation.adapters.XmlAdapter; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +/** + * Adapter to use java.time {@link Instant} when marshalling XML timestamps. + * + *

These fields shall contain timestamps indicating the date and time in UTC as specified in + * RFC3339, with no offset from the zero meridian. For example: {@code 2010-10-17T00:00:00Z}. + */ +public class UtcInstantAdapter extends XmlAdapter { + + private static final DateTimeFormatter MARSHAL_FORMAT = + DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneOffset.UTC); + + /** Same as {@link #marshal(Instant)}, but in a convenient static format. */ + public static String getFormattedString(@Nullable Instant timestamp) { + if (timestamp == null) { + return ""; + } + return MARSHAL_FORMAT.format(timestamp); + } + + /** + * Parses an ISO timestamp string into a UTC {@link Instant} object. If {@code timestamp} is empty + * or {@code null} then {@code null} is returned. + */ + @Nullable + @CheckForNull + @Override + public Instant unmarshal(@Nullable String timestamp) { + if (isNullOrEmpty(timestamp)) { + return null; + } + return parseInstant(timestamp); + } + + /** + * Converts {@link Instant} to UTC and returns it as an RFC3339 string. If {@code timestamp} is + * {@code null} then an empty string is returned. + */ + @Override + public String marshal(@Nullable Instant timestamp) { + return getFormattedString(timestamp); + } +} diff --git a/core/src/test/java/google/registry/batch/BulkDomainTransferActionTest.java b/core/src/test/java/google/registry/batch/BulkDomainTransferActionTest.java index eeb851c46..2d04513ac 100644 --- a/core/src/test/java/google/registry/batch/BulkDomainTransferActionTest.java +++ b/core/src/test/java/google/registry/batch/BulkDomainTransferActionTest.java @@ -36,6 +36,7 @@ import google.registry.testing.FakeClock; import google.registry.testing.FakeLockHandler; import google.registry.testing.FakeResponse; import google.registry.util.DateTimeUtils; +import java.time.Instant; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -89,39 +90,39 @@ public class BulkDomainTransferActionTest { assertThat(alreadyTransferredDomain.getCurrentSponsorRegistrarId()).isEqualTo("NewRegistrar"); assertThat(pendingDeleteDomain.getCurrentSponsorRegistrarId()).isEqualTo("TheRegistrar"); assertThat(deletedDomain.getCurrentSponsorRegistrarId()).isEqualTo("TheRegistrar"); - DateTime preRunTime = fakeClock.nowUtc(); + Instant preRunTime = fakeClock.now(); BulkDomainTransferAction action = createAction("active.tld", "alreadytransferred.tld", "pendingdelete.tld", "deleted.tld"); fakeClock.advanceOneMilli(); - DateTime runTime = fakeClock.nowUtc(); + Instant runTime = fakeClock.now(); action.run(); fakeClock.advanceOneMilli(); - DateTime now = fakeClock.nowUtc(); + Instant now = fakeClock.now(); // The active domain should have a new update timestamp and current registrar // The cloneProjectedAtTime calls are necessary to resolve the transfers, even though the // transfers have a time period of 0 activeDomain = loadByEntity(activeDomain); - assertThat(activeDomain.cloneProjectedAtTime(now).getCurrentSponsorRegistrarId()) + assertThat(activeDomain.cloneProjectedAtInstant(now).getCurrentSponsorRegistrarId()) .isEqualTo("NewRegistrar"); assertThat(activeDomain.getUpdateTimestamp().getTimestamp()).isEqualTo(runTime); // The other three domains shouldn't change alreadyTransferredDomain = loadByEntity(alreadyTransferredDomain); - assertThat(alreadyTransferredDomain.cloneProjectedAtTime(now).getCurrentSponsorRegistrarId()) + assertThat(alreadyTransferredDomain.cloneProjectedAtInstant(now).getCurrentSponsorRegistrarId()) .isEqualTo("NewRegistrar"); assertThat(alreadyTransferredDomain.getUpdateTimestamp().getTimestamp()).isEqualTo(preRunTime); pendingDeleteDomain = loadByEntity(pendingDeleteDomain); - assertThat(pendingDeleteDomain.cloneProjectedAtTime(now).getCurrentSponsorRegistrarId()) + assertThat(pendingDeleteDomain.cloneProjectedAtInstant(now).getCurrentSponsorRegistrarId()) .isEqualTo("TheRegistrar"); assertThat(pendingDeleteDomain.getUpdateTimestamp().getTimestamp()).isEqualTo(preRunTime); deletedDomain = loadByEntity(deletedDomain); - assertThat(deletedDomain.cloneProjectedAtTime(now).getCurrentSponsorRegistrarId()) + assertThat(deletedDomain.cloneProjectedAtInstant(now).getCurrentSponsorRegistrarId()) .isEqualTo("TheRegistrar"); assertThat(deletedDomain.getUpdateTimestamp().getTimestamp()).isEqualTo(preRunTime); } diff --git a/core/src/test/java/google/registry/batch/CheckBulkComplianceActionTest.java b/core/src/test/java/google/registry/batch/CheckBulkComplianceActionTest.java index 77e77561f..c7b841700 100644 --- a/core/src/test/java/google/registry/batch/CheckBulkComplianceActionTest.java +++ b/core/src/test/java/google/registry/batch/CheckBulkComplianceActionTest.java @@ -41,6 +41,7 @@ import google.registry.testing.DatabaseHelper; import google.registry.testing.FakeClock; import google.registry.ui.server.SendEmailUtils; import google.registry.util.EmailMessage; +import java.time.Instant; import java.util.logging.Level; import java.util.logging.Logger; import org.joda.money.CurrencyUnit; @@ -119,8 +120,8 @@ public class CheckBulkComplianceActionTest { .setMaxDomains(3) .setMaxCreates(1) .setBulkPrice(Money.of(CurrencyUnit.USD, 1000)) - .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) - .setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z")) + .setNextBillingDate(Instant.parse("2012-11-12T05:00:00Z")) + .setLastNotificationSent(Instant.parse("2010-11-12T05:00:00Z")) .build(); } @@ -213,7 +214,7 @@ public class CheckBulkComplianceActionTest { .setMaxDomains(8) .setMaxCreates(1) .setBulkPrice(Money.of(CurrencyUnit.USD, 1000)) - .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) + .setNextBillingDate(Instant.parse("2012-11-12T05:00:00Z")) .build(); tm().transact(() -> tm().put(bulkPricingPackage2)); @@ -271,7 +272,7 @@ public class CheckBulkComplianceActionTest { .setMaxDomains(8) .setMaxCreates(1) .setBulkPrice(Money.of(CurrencyUnit.USD, 1000)) - .setNextBillingDate(DateTime.parse("2015-11-12T05:00:00Z")) + .setNextBillingDate(Instant.parse("2015-11-12T05:00:00Z")) .build(); tm().transact(() -> tm().put(packagePromotion2)); @@ -347,7 +348,7 @@ public class CheckBulkComplianceActionTest { .setMaxDomains(8) .setMaxCreates(4) .setBulkPrice(Money.of(CurrencyUnit.USD, 1000)) - .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) + .setNextBillingDate(Instant.parse("2012-11-12T05:00:00Z")) .build(); tm().transact(() -> tm().put(bulkPricingPackage2)); persistEppResource( @@ -414,7 +415,7 @@ public class CheckBulkComplianceActionTest { .setMaxDomains(1) .setMaxCreates(5) .setBulkPrice(Money.of(CurrencyUnit.USD, 1000)) - .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) + .setNextBillingDate(Instant.parse("2012-11-12T05:00:00Z")) .build(); tm().transact(() -> tm().put(bulkPricingPackage2)); diff --git a/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java b/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java index 0315dedd6..00c6375c5 100644 --- a/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java +++ b/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java @@ -137,7 +137,7 @@ class DeleteExpiredDomainsActionTest { () -> tm() .createQueryComposer(Domain.class) - .where("autorenewEndTime", Comparator.LTE, clock.nowUtc()) + .where("autorenewEndTime", Comparator.LTE, clock.now()) .stream() .map(Domain::getDomainName) .collect(toImmutableSet())); diff --git a/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java b/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java index 2621c99eb..6399decde 100644 --- a/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java +++ b/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java @@ -27,7 +27,8 @@ import static google.registry.testing.DatabaseHelper.persistDeletedDomain; import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.util.DateTimeUtils.END_INSTANT; -import static org.joda.time.DateTimeZone.UTC; +import static google.registry.util.DateTimeUtils.minusYears; +import static google.registry.util.DateTimeUtils.toDateTime; import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableSet; @@ -63,7 +64,7 @@ class DeleteProberDataActionTest { private static final DateTime DELETION_TIME = DateTime.parse("2010-01-01T00:00:00.000Z"); - private final FakeClock clock = new FakeClock(DateTime.now(UTC)); + private final FakeClock clock = new FakeClock(Instant.parse("2021-01-01T00:00:00Z")); @RegisterExtension final JpaIntegrationTestExtension jpa = @@ -204,13 +205,13 @@ class DeleteProberDataActionTest { persistResource( DatabaseHelper.newDomain("blah.ib-any.test") .asBuilder() - .setCreationTimeForTest(DateTime.now(UTC).minusYears(1)) + .setCreationTimeForTest(minusYears(clock.now(), 1)) .build()); action.run(); - Instant timeAfterDeletion = Instant.now(); + Instant timeAfterDeletion = clock.now(); assertThat(ForeignKeyUtils.loadResource(Domain.class, "blah.ib-any.test", timeAfterDeletion)) .isEmpty(); - assertThat(loadByEntity(domain).getDeletionTime()).isLessThan(timeAfterDeletion); + assertThat(loadByEntity(domain).getDeletionTime()).isAtMost(timeAfterDeletion); assertDomainDnsRequests("blah.ib-any.test"); } @@ -220,15 +221,15 @@ class DeleteProberDataActionTest { persistResource( DatabaseHelper.newDomain("blah.ib-any.test") .asBuilder() - .setCreationTimeForTest(DateTime.now(UTC).minusYears(1)) + .setCreationTimeForTest(minusYears(clock.now(), 1)) .build()); action.run(); - Instant timeAfterDeletion = Instant.now(); + Instant timeAfterDeletion = clock.now(); resetAction(); action.run(); assertThat(ForeignKeyUtils.loadResource(Domain.class, "blah.ib-any.test", timeAfterDeletion)) .isEmpty(); - assertThat(loadByEntity(domain).getDeletionTime()).isLessThan(timeAfterDeletion); + assertThat(loadByEntity(domain).getDeletionTime()).isAtMost(timeAfterDeletion); assertDomainDnsRequests("blah.ib-any.test"); } @@ -237,11 +238,11 @@ class DeleteProberDataActionTest { persistResource( DatabaseHelper.newDomain("blah.ib-any.test") .asBuilder() - .setCreationTimeForTest(DateTime.now(UTC).minusSeconds(1)) + .setCreationTimeForTest(clock.now().minus(java.time.Duration.ofSeconds(1))) .build()); action.run(); Optional domain = - ForeignKeyUtils.loadResource(Domain.class, "blah.ib-any.test", DateTime.now(UTC)); + ForeignKeyUtils.loadResource(Domain.class, "blah.ib-any.test", clock.now()); assertThat(domain).isPresent(); assertThat(domain.get().getDeletionTime()).isEqualTo(END_INSTANT); } @@ -252,7 +253,7 @@ class DeleteProberDataActionTest { persistResource( DatabaseHelper.newDomain("blah.ib-any.test") .asBuilder() - .setCreationTimeForTest(DateTime.now(UTC).minusYears(1)) + .setCreationTimeForTest(minusYears(clock.now(), 1)) .build()); action.isDryRun = true; action.run(); @@ -263,14 +264,14 @@ class DeleteProberDataActionTest { void test_domainWithSubordinateHosts_isSkipped() throws Exception { persistActiveHost("ns1.blah.ib-any.test"); Domain nakedDomain = - persistDeletedDomain("todelete.ib-any.test", DateTime.now(UTC).minusYears(1)); + persistDeletedDomain("todelete.ib-any.test", toDateTime(minusYears(clock.now(), 1))); Domain domainWithSubord = persistDomainAsDeleted( DatabaseHelper.newDomain("blah.ib-any.test") .asBuilder() .setSubordinateHosts(ImmutableSet.of("ns1.blah.ib-any.test")) .build(), - DateTime.now(UTC).minusYears(1)); + toDateTime(minusYears(clock.now(), 1))); action.run(); assertAllExist(ImmutableSet.of(domainWithSubord)); @@ -282,7 +283,7 @@ class DeleteProberDataActionTest { persistResource( DatabaseHelper.newDomain("blah.ib-any.test") .asBuilder() - .setCreationTimeForTest(DateTime.now(UTC).minusYears(1)) + .setCreationTimeForTest(minusYears(clock.now(), 1)) .build()); action.registryAdminRegistrarId = null; IllegalStateException thrown = assertThrows(IllegalStateException.class, action::run); diff --git a/core/src/test/java/google/registry/batch/ExpandBillingRecurrencesActionTest.java b/core/src/test/java/google/registry/batch/ExpandBillingRecurrencesActionTest.java index 7e8c07d5c..2bda1be8f 100644 --- a/core/src/test/java/google/registry/batch/ExpandBillingRecurrencesActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandBillingRecurrencesActionTest.java @@ -16,6 +16,7 @@ package google.registry.batch; import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.DateTimeUtils.toDateTime; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; @@ -31,9 +32,9 @@ import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.testing.FakeClock; import java.io.IOException; +import java.time.Instant; import java.util.HashMap; import java.util.Optional; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -42,8 +43,8 @@ import org.mockito.ArgumentCaptor; /** Unit tests for {@link ExpandBillingRecurrencesAction}. */ public class ExpandBillingRecurrencesActionTest extends BeamActionTestBase { - private final DateTime cursorTime = DateTime.parse("2020-02-01T00:00:00Z"); - private final DateTime now = DateTime.parse("2020-02-02T00:00:00Z"); + private final Instant cursorTime = Instant.parse("2020-02-01T00:00:00Z"); + private final Instant now = Instant.parse("2020-02-02T00:00:00Z"); private final FakeClock clock = new FakeClock(now); private final ExpandBillingRecurrencesAction action = new ExpandBillingRecurrencesAction(); @@ -69,11 +70,14 @@ public class ExpandBillingRecurrencesActionTest extends BeamActionTestBase { action.dataflow = dataflow; action.response = response; expectedParameters.put("registryEnvironment", "UNITTEST"); - expectedParameters.put("startTime", "2020-02-01T00:00:00.000Z"); - expectedParameters.put("endTime", "2020-02-02T00:00:00.000Z"); + expectedParameters.put("startTime", "2020-02-01T00:00:00Z"); + expectedParameters.put("endTime", "2020-02-02T00:00:00Z"); expectedParameters.put("isDryRun", "false"); expectedParameters.put("advanceCursor", "true"); - tm().transact(() -> tm().put(Cursor.createGlobal(CursorType.RECURRING_BILLING, cursorTime))); + tm().transact( + () -> + tm().put( + Cursor.createGlobal(CursorType.RECURRING_BILLING, toDateTime(cursorTime)))); } @Test @@ -89,7 +93,7 @@ public class ExpandBillingRecurrencesActionTest extends BeamActionTestBase { @Test void testSuccess_provideEndTime() throws Exception { - action.endTimeParam = Optional.of(DateTime.parse("2020-02-01T12:00:00.001Z")); + action.endTimeParam = Optional.of(Instant.parse("2020-02-01T12:00:00.001Z")); expectedParameters.put("endTime", "2020-02-01T12:00:00.001Z"); action.run(); assertThat(response.getStatus()).isEqualTo(200); @@ -102,7 +106,7 @@ public class ExpandBillingRecurrencesActionTest extends BeamActionTestBase { @Test void testSuccess_provideStartTime() throws Exception { - action.startTimeParam = Optional.of(DateTime.parse("2020-01-01T12:00:00.001Z")); + action.startTimeParam = Optional.of(Instant.parse("2020-01-01T12:00:00.001Z")); expectedParameters.put("startTime", "2020-01-01T12:00:00.001Z"); action.run(); assertThat(response.getStatus()).isEqualTo(200); @@ -143,7 +147,7 @@ public class ExpandBillingRecurrencesActionTest extends BeamActionTestBase { @Test void testFailure_endTimeAfterNow() throws Exception { - action.endTimeParam = Optional.of(DateTime.parse("2020-02-03T00:00:00Z")); + action.endTimeParam = Optional.of(Instant.parse("2020-02-03T00:00:00Z")); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> action.run()); assertThat(thrown.getMessage()).contains("must be at or before now"); @@ -152,7 +156,7 @@ public class ExpandBillingRecurrencesActionTest extends BeamActionTestBase { @Test void testFailure_startTimeAfterEndTime() throws Exception { - action.startTimeParam = Optional.of(DateTime.parse("2020-02-03T00:00:00Z")); + action.startTimeParam = Optional.of(Instant.parse("2020-02-03T00:00:00Z")); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> action.run()); assertThat(thrown.getMessage()).contains("must be before end time"); diff --git a/core/src/test/java/google/registry/beam/billing/ExpandBillingRecurrencesPipelineTest.java b/core/src/test/java/google/registry/beam/billing/ExpandBillingRecurrencesPipelineTest.java index b23295921..14f60c07f 100644 --- a/core/src/test/java/google/registry/beam/billing/ExpandBillingRecurrencesPipelineTest.java +++ b/core/src/test/java/google/registry/beam/billing/ExpandBillingRecurrencesPipelineTest.java @@ -29,7 +29,12 @@ import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.minusYears; +import static google.registry.util.DateTimeUtils.plusDays; +import static google.registry.util.DateTimeUtils.plusYears; +import static google.registry.util.DateTimeUtils.toDateTime; +import static google.registry.util.DateTimeUtils.toInstant; import static org.joda.money.CurrencyUnit.USD; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -52,6 +57,9 @@ import google.registry.persistence.PersistenceModule.TransactionIsolationLevel; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.testing.FakeClock; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -61,9 +69,6 @@ import org.apache.beam.sdk.options.PipelineOptionsFactory; import org.hibernate.cfg.AvailableSettings; import org.joda.money.Money; import org.joda.time.DateTime; -import org.joda.time.Duration; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -73,16 +78,13 @@ import org.junit.jupiter.params.provider.ValueSource; /** Unit tests for {@link ExpandBillingRecurrencesPipeline}. */ public class ExpandBillingRecurrencesPipelineTest { - private static final DateTimeFormatter DATE_TIME_FORMATTER = - DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + private final FakeClock clock = new FakeClock(Instant.parse("2021-02-02T00:00:05.000Z")); - private final FakeClock clock = new FakeClock(DateTime.parse("2021-02-02T00:00:05Z")); + private final Instant startTime = Instant.parse("2021-02-01T00:00:00.000Z"); - private final DateTime startTime = DateTime.parse("2021-02-01TZ"); + private Instant endTime = Instant.parse("2021-02-02T00:00:00.000Z"); - private DateTime endTime = DateTime.parse("2021-02-02TZ"); - - private final Cursor cursor = Cursor.createGlobal(RECURRING_BILLING, startTime); + private final Cursor cursor = Cursor.createGlobal(RECURRING_BILLING, toDateTime(startTime)); private Domain domain; @@ -106,21 +108,22 @@ public class ExpandBillingRecurrencesPipelineTest { @BeforeEach void beforeEach() { // Set up the pipeline. - options.setStartTime(DATE_TIME_FORMATTER.print(startTime)); - options.setEndTime(DATE_TIME_FORMATTER.print(endTime)); + options.setStartTime(startTime.toString()); + options.setEndTime(endTime.toString()); options.setIsDryRun(false); options.setAdvanceCursor(true); tm().transact(() -> tm().put(cursor)); // Set up the database. createTld("tld"); - billingRecurrence = createDomainAtTime("example.tld", startTime.minusYears(1).plusHours(12)); - domain = ForeignKeyUtils.loadResource(Domain.class, "example.tld", clock.nowUtc()).get(); + billingRecurrence = + createDomainAtTime("example.tld", minusYears(startTime, 1).plus(12, ChronoUnit.HOURS)); + domain = ForeignKeyUtils.loadResource(Domain.class, "example.tld", clock.now()).get(); } @Test void testFailure_endTimeAfterNow() { - options.setEndTime(DATE_TIME_FORMATTER.print(clock.nowUtc().plusMillis(1))); + options.setEndTime(clock.now().plus(1, ChronoUnit.MILLIS).toString()); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, this::runPipeline); assertThat(thrown) @@ -130,12 +133,12 @@ public class ExpandBillingRecurrencesPipelineTest { @Test void testFailure_endTimeBeforeStartTime() { - options.setEndTime(DATE_TIME_FORMATTER.print(startTime.minusMillis(1))); + options.setEndTime(startTime.minus(1, ChronoUnit.MILLIS).toString()); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, this::runPipeline); assertThat(thrown) .hasMessageThat() - .contains("[2021-02-01T00:00:00.000Z, 2021-01-31T23:59:59.999Z)"); + .contains("[2021-02-01T00:00:00Z, 2021-01-31T23:59:59.999Z)"); } @Test @@ -151,7 +154,7 @@ public class ExpandBillingRecurrencesPipelineTest { defaultOneTime(getOnlyAutoRenewHistory()), billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(domain.getCreationTime().plusYears(1)) + .setRecurrenceLastExpansion(plusYears(domain.getCreationTimeInstant(), 1)) .build()); // Assert about Cursor. @@ -160,10 +163,15 @@ public class ExpandBillingRecurrencesPipelineTest { @Test void testSuccess_expandSingleEvent_deletedDuringGracePeriod() { - domain = persistResource(domain.asBuilder().setDeletionTime(endTime.minusHours(2)).build()); + domain = + persistResource( + domain.asBuilder().setDeletionTime(endTime.minus(Duration.ofHours(2))).build()); billingRecurrence = persistResource( - billingRecurrence.asBuilder().setRecurrenceEndTime(endTime.minusHours(2)).build()); + billingRecurrence + .asBuilder() + .setRecurrenceEndTime(endTime.minus(Duration.ofHours(2))) + .build()); runPipeline(); // Assert about DomainHistory, no transaction record should have been written. @@ -176,7 +184,7 @@ public class ExpandBillingRecurrencesPipelineTest { defaultOneTime(getOnlyAutoRenewHistory()), billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(domain.getCreationTime().plusYears(1)) + .setRecurrenceLastExpansion(plusYears(domain.getCreationTimeInstant(), 1)) .build()); // Assert about Cursor. @@ -185,7 +193,11 @@ public class ExpandBillingRecurrencesPipelineTest { @Test void testFailure_expandSingleEvent_cursorNotAtStartTime() { - tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, startTime.plusMillis(1)))); + tm().transact( + () -> + tm().put( + Cursor.createGlobal( + RECURRING_BILLING, toDateTime(startTime.plusMillis(1))))); PipelineExecutionException thrown = assertThrows(PipelineExecutionException.class, this::runPipeline); @@ -201,7 +213,7 @@ public class ExpandBillingRecurrencesPipelineTest { defaultOneTime(getOnlyAutoRenewHistory()), billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(domain.getCreationTime().plusYears(1)) + .setRecurrenceLastExpansion(plusYears(domain.getCreationTimeInstant(), 1)) .build()); // Assert that the cursor did not change. @@ -214,7 +226,8 @@ public class ExpandBillingRecurrencesPipelineTest { persistResource( billingRecurrence .asBuilder() - .setRecurrenceEndTime(billingRecurrence.getEventTime().minusDays(1)) + .setRecurrenceEndTime( + billingRecurrence.getEventTimeInstant().minus(1, ChronoUnit.DAYS)) .build()); runPipeline(); assertNoExpansionsHappened(); @@ -224,7 +237,10 @@ public class ExpandBillingRecurrencesPipelineTest { void testSuccess_noExpansion_recurrenceClosedBeforeStartTime() { billingRecurrence = persistResource( - billingRecurrence.asBuilder().setRecurrenceEndTime(startTime.minusDays(1)).build()); + billingRecurrence + .asBuilder() + .setRecurrenceEndTime(startTime.minus(1, ChronoUnit.DAYS)) + .build()); runPipeline(); assertNoExpansionsHappened(); } @@ -235,8 +251,8 @@ public class ExpandBillingRecurrencesPipelineTest { persistResource( billingRecurrence .asBuilder() - .setEventTime(billingRecurrence.getEventTime().minusYears(1)) - .setRecurrenceEndTime(startTime.plusHours(6)) + .setEventTime(minusYears(billingRecurrence.getEventTimeInstant(), 1)) + .setRecurrenceEndTime(startTime.plus(6, ChronoUnit.HOURS)) .build()); runPipeline(); assertNoExpansionsHappened(); @@ -245,7 +261,7 @@ public class ExpandBillingRecurrencesPipelineTest { @Test void testSuccess_noExpansion_eventTimeAfterEndTime() { billingRecurrence = - persistResource(billingRecurrence.asBuilder().setEventTime(endTime.plusDays(1)).build()); + persistResource(billingRecurrence.asBuilder().setEventTime(plusDays(endTime, 1)).build()); runPipeline(); assertNoExpansionsHappened(); } @@ -256,7 +272,7 @@ public class ExpandBillingRecurrencesPipelineTest { persistResource( billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(startTime.minusYears(1).plusDays(1)) + .setRecurrenceLastExpansion(plusDays(minusYears(startTime, 1), 1)) .build()); runPipeline(); assertNoExpansionsHappened(); @@ -300,7 +316,7 @@ public class ExpandBillingRecurrencesPipelineTest { defaultOneTime(getOnlyAutoRenewHistory()), billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(domain.getCreationTime().plusYears(1)) + .setRecurrenceLastExpansion(plusYears(domain.getCreationTimeInstant(), 1)) .build()); // Assert that the cursor did not move. @@ -319,10 +335,10 @@ public class ExpandBillingRecurrencesPipelineTest { .asBuilder() .setPremiumList(persistPremiumList("premium", USD, "other,USD 100")) .build()); - DateTime otherCreateTime = startTime.minusYears(1).plusHours(5); + Instant otherCreateTime = minusYears(startTime, 1).plus(5, ChronoUnit.HOURS); BillingRecurrence otherBillingRecurrence = createDomainAtTime("other.test", otherCreateTime); Domain otherDomain = - ForeignKeyUtils.loadResource(Domain.class, "other.test", clock.nowUtc()).get(); + ForeignKeyUtils.loadResource(Domain.class, "other.test", clock.now()).get(); options.setTargetParallelism(numOfThreads); runPipeline(); @@ -339,7 +355,7 @@ public class ExpandBillingRecurrencesPipelineTest { defaultOneTime(getOnlyAutoRenewHistory()), billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(domain.getCreationTime().plusYears(1)) + .setRecurrenceLastExpansion(plusYears(domain.getCreationTimeInstant(), 1)) .build()); assertBillingEventsForResource( otherDomain, @@ -347,7 +363,7 @@ public class ExpandBillingRecurrencesPipelineTest { otherDomain, getOnlyAutoRenewHistory(otherDomain), otherBillingRecurrence, 100), otherBillingRecurrence .asBuilder() - .setRecurrenceLastExpansion(otherDomain.getCreationTime().plusYears(1)) + .setRecurrenceLastExpansion(plusYears(otherDomain.getCreationTimeInstant(), 1)) .build()); // Assert about Cursor. @@ -356,9 +372,9 @@ public class ExpandBillingRecurrencesPipelineTest { @Test void testSuccess_expandMultipleEvents_multipleEventTime() { - clock.advanceBy(Duration.standardDays(365)); - endTime = endTime.plusYears(1); - options.setEndTime(DATE_TIME_FORMATTER.print(endTime)); + clock.advanceBy(org.joda.time.Duration.standardDays(365)); + endTime = plusYears(endTime, 1); + options.setEndTime(endTime.toString()); runPipeline(); // Assert about DomainHistory. @@ -371,10 +387,9 @@ public class ExpandBillingRecurrencesPipelineTest { DomainTransactionRecord.create( domain.getTld(), // We report this when the autorenew grace period ends. - domain - .getCreationTime() - .plusYears(2) - .plus(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD), + plusYears(domain.getCreationTimeInstant(), 2) + .plus( + Duration.ofMillis(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD.getMillis())), TransactionReportField.netRenewsFieldFromYears(1), 1))) .build()); @@ -389,20 +404,21 @@ public class ExpandBillingRecurrencesPipelineTest { h.getDomainTransactionRecords().stream() .findFirst() .get() - .getReportingTime())) + .getReportingTimeInstant())) .collect(toImmutableList()); assertBillingEventsForResource( domain, defaultOneTime(histories.get(0)), defaultOneTime(histories.get(1)) .asBuilder() - .setEventTime(domain.getCreationTime().plusYears(2)) + .setEventTime(plusYears(domain.getCreationTimeInstant(), 2)) .setBillingTime( - domain.getCreationTime().plusYears(2).plus(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD)) + plusYears(domain.getCreationTimeInstant(), 2) + .plus(Duration.ofMillis(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD.getMillis()))) .build(), billingRecurrence .asBuilder() - .setRecurrenceLastExpansion(domain.getCreationTime().plusYears(2)) + .setRecurrenceLastExpansion(plusYears(domain.getCreationTimeInstant(), 2)) .build()); // Assert about Cursor. @@ -445,7 +461,7 @@ public class ExpandBillingRecurrencesPipelineTest { return new DomainHistory.Builder() .setBySuperuser(false) .setRegistrarId("TheRegistrar") - .setModificationTime(clock.nowUtc()) + .setModificationTime(clock.now()) .setDomain(domain) .setPeriod(Period.create(1, YEARS)) .setReason("Domain autorenewal by ExpandRecurringBillingEventsPipeline") @@ -456,7 +472,8 @@ public class ExpandBillingRecurrencesPipelineTest { DomainTransactionRecord.create( domain.getTld(), // We report this when the autorenew grace period ends. - domain.getCreationTime().plusYears(1).plus(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD), + plusYears(domain.getCreationTimeInstant(), 1) + .plus(Duration.ofMillis(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD.getMillis())), TransactionReportField.netRenewsFieldFromYears(1), 1))) .build(); @@ -470,10 +487,11 @@ public class ExpandBillingRecurrencesPipelineTest { Domain domain, DomainHistory history, BillingRecurrence billingRecurrence, int cost) { return new BillingEvent.Builder() .setBillingTime( - domain.getCreationTime().plusYears(1).plus(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD)) + plusYears(domain.getCreationTimeInstant(), 1) + .plus(Duration.ofMillis(Tld.DEFAULT_AUTO_RENEW_GRACE_PERIOD.getMillis()))) .setRegistrarId("TheRegistrar") .setCost(Money.of(USD, cost)) - .setEventTime(domain.getCreationTime().plusYears(1)) + .setEventTime(plusYears(domain.getCreationTimeInstant(), 1)) .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) .setPeriodYears(1) .setReason(Reason.RENEW) @@ -515,14 +533,18 @@ public class ExpandBillingRecurrencesPipelineTest { return getOnlyAutoRenewHistory(domain); } - private static void assertCursorAt(DateTime expectedCursorTime) { + private static void assertCursorAt(Instant expectedCursorTime) { Cursor cursor = tm().transact(() -> tm().loadByKey(Cursor.createGlobalVKey(RECURRING_BILLING))); assertThat(cursor).isNotNull(); - assertThat(cursor.getCursorTime()).isEqualTo(expectedCursorTime); + assertThat(cursor.getCursorTimeInstant()).isEqualTo(expectedCursorTime); } - private static BillingRecurrence createDomainAtTime(String domainName, DateTime createTime) { - Domain domain = persistActiveDomain(domainName, createTime); + private static void assertCursorAt(DateTime expectedCursorTime) { + assertCursorAt(toInstant(expectedCursorTime)); + } + + private static BillingRecurrence createDomainAtTime(String domainName, Instant createTime) { + Domain domain = persistActiveDomain(domainName, toDateTime(createTime)); DomainHistory domainHistory = persistResource( new DomainHistory.Builder() @@ -535,10 +557,10 @@ public class ExpandBillingRecurrencesPipelineTest { new BillingRecurrence.Builder() .setDomainHistory(domainHistory) .setRegistrarId(domain.getCreationRegistrarId()) - .setEventTime(createTime.plusYears(1)) + .setEventTime(plusYears(createTime, 1)) .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) .setReason(Reason.RENEW) - .setRecurrenceEndTime(END_OF_TIME) + .setRecurrenceEndTime(END_INSTANT) .setTargetId(domain.getDomainName()) .build()); } diff --git a/core/src/test/java/google/registry/beam/resave/ResaveAllEppResourcesPipelineTest.java b/core/src/test/java/google/registry/beam/resave/ResaveAllEppResourcesPipelineTest.java index b9d8f59a5..0343eb0f3 100644 --- a/core/src/test/java/google/registry/beam/resave/ResaveAllEppResourcesPipelineTest.java +++ b/core/src/test/java/google/registry/beam/resave/ResaveAllEppResourcesPipelineTest.java @@ -26,7 +26,7 @@ import static google.registry.testing.DatabaseHelper.persistDomainWithDependentR import static google.registry.testing.DatabaseHelper.persistDomainWithPendingTransfer; import static google.registry.testing.DatabaseHelper.persistNewRegistrars; import static google.registry.util.DateTimeUtils.plusYears; -import static google.registry.util.DateTimeUtils.toInstant; +import static google.registry.util.DateTimeUtils.toDateTime; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -43,6 +43,8 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT import google.registry.persistence.transaction.JpaTransactionManager; import google.registry.persistence.transaction.TransactionManagerFactory; import google.registry.testing.FakeClock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import org.apache.beam.sdk.options.PipelineOptionsFactory; import org.hibernate.cfg.Environment; import org.joda.time.DateTime; @@ -82,7 +84,7 @@ public class ResaveAllEppResourcesPipelineTest { @Test void testPipeline_unchangedEntity() { Host host = persistActiveHost("ns1.example.tld"); - DateTime creationTime = host.getUpdateTimestamp().getTimestamp(); + Instant creationTime = host.getUpdateTimestamp().getTimestamp(); fakeClock.advanceOneMilli(); assertThat(loadByEntity(host).getUpdateTimestamp().getTimestamp()).isEqualTo(creationTime); fakeClock.advanceOneMilli(); @@ -93,51 +95,57 @@ public class ResaveAllEppResourcesPipelineTest { @Test void testPipeline_fulfilledDomainTransfer() { options.setFast(true); - DateTime now = fakeClock.nowUtc(); + Instant now = fakeClock.now(); Domain domain = persistDomainWithPendingTransfer( persistDomainWithDependentResources( - "domain", "tld", now.minusDays(5), now.minusDays(5), now.plusYears(2)), - now.minusDays(4), - now.minusDays(1), - now.plusYears(2)); + "domain", + "tld", + toDateTime(now.minus(5, ChronoUnit.DAYS)), + toDateTime(now.minus(5, ChronoUnit.DAYS)), + toDateTime(plusYears(now, 2))), + toDateTime(now.minus(4, ChronoUnit.DAYS)), + toDateTime(now.minus(1, ChronoUnit.DAYS)), + toDateTime(plusYears(now, 2))); assertThat(domain.getStatusValues()).contains(StatusValue.PENDING_TRANSFER); assertThat(domain.getUpdateTimestamp().getTimestamp()).isEqualTo(now); fakeClock.advanceOneMilli(); runPipeline(); Domain postPipeline = loadByEntity(domain); assertThat(postPipeline.getStatusValues()).doesNotContain(StatusValue.PENDING_TRANSFER); - assertThat(postPipeline.getUpdateTimestamp().getTimestamp()).isEqualTo(fakeClock.nowUtc()); + assertThat(postPipeline.getUpdateTimestamp().getTimestamp()).isEqualTo(fakeClock.now()); } @Test void testPipeline_autorenewedDomain() { - DateTime now = fakeClock.nowUtc(); + Instant now = fakeClock.now(); Domain domain = - persistDomainWithDependentResources("domain", "tld", now, now, now.plusYears(1)); - assertThat(domain.getRegistrationExpirationTime()).isEqualTo(plusYears(toInstant(now), 1)); + persistDomainWithDependentResources( + "domain", "tld", toDateTime(now), toDateTime(now), toDateTime(plusYears(now, 1))); + assertThat(domain.getRegistrationExpirationTime()).isEqualTo(plusYears(now, 1)); fakeClock.advanceBy(Duration.standardDays(500)); runPipeline(); Domain postPipeline = loadByEntity(domain); - assertThat(postPipeline.getRegistrationExpirationTime()) - .isEqualTo(plusYears(toInstant(now), 2)); + assertThat(postPipeline.getRegistrationExpirationTime()).isEqualTo(plusYears(now, 2)); } @Test void testPipeline_expiredGracePeriod() { - DateTime now = fakeClock.nowUtc(); - persistDomainWithDependentResources("domain", "tld", now, now, now.plusYears(1)); + Instant now = fakeClock.now(); + persistDomainWithDependentResources( + "domain", "tld", toDateTime(now), toDateTime(now), toDateTime(plusYears(now, 1))); assertThat(loadAllOf(GracePeriod.class)).hasSize(1); - fakeClock.advanceBy(Duration.standardDays(500)); + fakeClock.advanceBy(org.joda.time.Duration.standardDays(500)); runPipeline(); assertThat(loadAllOf(GracePeriod.class)).isEmpty(); } @Test void testPipeline_fastOnlySavesChanged() { - DateTime now = fakeClock.nowUtc(); - persistDomainWithDependentResources("renewed", "tld", now, now, now.plusYears(1)); - persistActiveDomain("nonrenewed.tld", now, now.plusYears(20)); + Instant now = fakeClock.now(); + persistDomainWithDependentResources( + "renewed", "tld", toDateTime(now), toDateTime(now), toDateTime(plusYears(now, 1))); + persistActiveDomain("nonrenewed.tld", toDateTime(now), toDateTime(plusYears(now, 20))); // Spy the transaction manager so we can be sure we're only saving the renewed domain JpaTransactionManager spy = spy(tm()); TransactionManagerFactory.setJpaTm(() -> spy); diff --git a/core/src/test/java/google/registry/bsa/BsaValidateActionTest.java b/core/src/test/java/google/registry/bsa/BsaValidateActionTest.java index 16bd37e23..745f1126a 100644 --- a/core/src/test/java/google/registry/bsa/BsaValidateActionTest.java +++ b/core/src/test/java/google/registry/bsa/BsaValidateActionTest.java @@ -25,7 +25,8 @@ import static google.registry.bsa.persistence.BsaTestingUtils.persistUnblockable import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.startsWith; @@ -56,10 +57,10 @@ import google.registry.testing.FakeClock; import google.registry.tldconfig.idn.IdnTableEnum; import google.registry.util.EmailMessage; import jakarta.mail.internet.InternetAddress; +import java.time.Duration; +import java.time.Instant; import java.util.Optional; import java.util.stream.Stream; -import org.joda.time.DateTime; -import org.joda.time.Duration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -75,9 +76,9 @@ public class BsaValidateActionTest { private static final String DOWNLOAD_JOB_NAME = "job"; - private static final Duration MAX_STALENESS = Duration.standardMinutes(1); + private static final Duration MAX_STALENESS = Duration.ofMinutes(1); - FakeClock fakeClock = new FakeClock(DateTime.parse("2023-11-09T02:08:57.880Z")); + FakeClock fakeClock = new FakeClock(Instant.parse("2023-11-09T02:08:57.880Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = @@ -107,7 +108,7 @@ public class BsaValidateActionTest { idnChecker, new BsaEmailSender(gmailClient, emailRecipient), /* transactionBatchSize= */ 500, - MAX_STALENESS, + org.joda.time.Duration.millis(MAX_STALENESS.toMillis()), fakeClock, response); createTld("app"); @@ -237,16 +238,17 @@ public class BsaValidateActionTest { @Test void isStalenessAllowed_newDomain_allowed() { persistBsaLabel("label"); - Domain domain = persistActiveDomain("label.app", fakeClock.nowUtc()); - fakeClock.advanceBy(MAX_STALENESS.minus(Duration.standardSeconds(1))); + Domain domain = persistActiveDomain("label.app", toDateTime(fakeClock.now())); + fakeClock.advanceBy( + org.joda.time.Duration.millis(MAX_STALENESS.minus(Duration.ofSeconds(1)).toMillis())); assertThat(action.isStalenessAllowed(domain)).isTrue(); } @Test void isStalenessAllowed_newDomain_notAllowed() { persistBsaLabel("label"); - Domain domain = persistActiveDomain("label.app", fakeClock.nowUtc()); - fakeClock.advanceBy(MAX_STALENESS); + Domain domain = persistActiveDomain("label.app", toDateTime(fakeClock.now())); + fakeClock.advanceBy(org.joda.time.Duration.millis(MAX_STALENESS.toMillis())); assertThat(action.isStalenessAllowed(domain)).isFalse(); } @@ -276,9 +278,15 @@ public class BsaValidateActionTest { @Test void checkForMissingReservedUnblockables_success() { persistResource( - createTld("app").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + createTld("app") + .asBuilder() + .setBsaEnrollStartTime(Optional.of(toDateTime(START_INSTANT))) + .build()); persistResource( - createTld("dev").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + createTld("dev") + .asBuilder() + .setBsaEnrollStartTime(Optional.of(toDateTime(START_INSTANT))) + .build()); persistBsaLabel("registered-reserved"); persistBsaLabel("reserved-only"); persistBsaLabel("reserved-missing"); @@ -294,7 +302,7 @@ public class BsaValidateActionTest { .collect(toImmutableMap(x -> x, x -> ReservationType.RESERVED_FOR_SPECIFIC_USE))); addReservedListsToTld("app", ImmutableList.of("rl")); - ImmutableList errors = action.checkForMissingReservedUnblockables(fakeClock.nowUtc()); + ImmutableList errors = action.checkForMissingReservedUnblockables(fakeClock.now()); assertThat(errors) .containsExactly("Missing unblockable domain: reserved-missing.app is reserved."); } @@ -302,9 +310,15 @@ public class BsaValidateActionTest { @Test void checkForMissingReservedUnblockablesInOneTld_success() { persistResource( - createTld("app").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + createTld("app") + .asBuilder() + .setBsaEnrollStartTime(Optional.of(toDateTime(START_INSTANT))) + .build()); persistResource( - createTld("dev").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + createTld("dev") + .asBuilder() + .setBsaEnrollStartTime(Optional.of(toDateTime(START_INSTANT))) + .build()); persistBsaLabel("reserved-missing-in-app"); persistUnblockableDomain( UnblockableDomain.of("reserved-missing-in-app", "dev", Reason.REGISTERED)); @@ -316,7 +330,7 @@ public class BsaValidateActionTest { addReservedListsToTld("app", ImmutableList.of("rl")); addReservedListsToTld("dev", ImmutableList.of("rl")); - ImmutableList errors = action.checkForMissingReservedUnblockables(fakeClock.nowUtc()); + ImmutableList errors = action.checkForMissingReservedUnblockables(fakeClock.now()); assertThat(errors) .containsExactly("Missing unblockable domain: reserved-missing-in-app.app is reserved."); } @@ -324,7 +338,10 @@ public class BsaValidateActionTest { @Test void checkForMissingReservedUnblockables_unblockedReservedNotReported() { persistResource( - createTld("app").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + createTld("app") + .asBuilder() + .setBsaEnrollStartTime(Optional.of(toDateTime(START_INSTANT))) + .build()); createReservedList( "rl", @@ -332,14 +349,17 @@ public class BsaValidateActionTest { .collect(toImmutableMap(x -> x, x -> ReservationType.RESERVED_FOR_SPECIFIC_USE))); addReservedListsToTld("app", ImmutableList.of("rl")); - ImmutableList errors = action.checkForMissingReservedUnblockables(fakeClock.nowUtc()); + ImmutableList errors = action.checkForMissingReservedUnblockables(fakeClock.now()); assertThat(errors).isEmpty(); } @Test void checkForMissingRegisteredUnblockables_success() { persistResource( - createTld("app").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()); + createTld("app") + .asBuilder() + .setBsaEnrollStartTime(Optional.of(toDateTime(START_INSTANT))) + .build()); persistBsaLabel("registered"); persistBsaLabel("registered-missing"); persistUnblockableDomain(UnblockableDomain.of("registered", "app", Reason.REGISTERED)); @@ -347,7 +367,7 @@ public class BsaValidateActionTest { persistActiveDomain("registered.app"); persistActiveDomain("registered-missing.app"); - ImmutableList errors = action.checkForMissingRegisteredUnblockables(fakeClock.nowUtc()); + ImmutableList errors = action.checkForMissingRegisteredUnblockables(fakeClock.now()); assertThat(errors) .containsExactly( "Registered domain registered-missing.app missing or not recorded as REGISTERED"); diff --git a/core/src/test/java/google/registry/bsa/UploadBsaUnavailableDomainsActionTest.java b/core/src/test/java/google/registry/bsa/UploadBsaUnavailableDomainsActionTest.java index 4fc751ff9..4e72f4f3b 100644 --- a/core/src/test/java/google/registry/bsa/UploadBsaUnavailableDomainsActionTest.java +++ b/core/src/test/java/google/registry/bsa/UploadBsaUnavailableDomainsActionTest.java @@ -127,7 +127,7 @@ public class UploadBsaUnavailableDomainsActionTest { persistDeletedDomain("not-blocked.tld", clock.nowUtc().minusDays(1)); action.run(); BlobId existingFile = - BlobId.of(BUCKET, String.format("unavailable_domains_%s.txt", clock.nowUtc())); + BlobId.of(BUCKET, String.format("unavailable_domains_%s.txt", clock.now())); String blockList = new String(gcsUtils.readBytesFrom(existingFile), UTF_8); assertThat(blockList).isEqualTo("ace.tld\nflagrant.tld\nfoobar.tld\njimmy.tld\ntine.tld\n"); assertThat(blockList).doesNotContain("not-blocked.tld"); diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaDomainRefreshTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaDomainRefreshTest.java index b0d19db00..e9acb4368 100644 --- a/core/src/test/java/google/registry/bsa/persistence/BsaDomainRefreshTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/BsaDomainRefreshTest.java @@ -40,7 +40,8 @@ public class BsaDomainRefreshTest { BsaDomainRefresh persisted = tm().transact(() -> tm().getEntityManager().merge(new BsaDomainRefresh())); assertThat(persisted.jobId).isNotNull(); - assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.nowUtc()); + assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.now()); + assertThat(persisted.updateTime.getTimestamp()).isEqualTo(fakeClock.now()); assertThat(persisted.stage).isEqualTo(CHECK_FOR_CHANGES); } diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java index 3dd034313..81606299e 100644 --- a/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java @@ -43,7 +43,7 @@ public class BsaDownloadTest { void saveJob() { BsaDownload persisted = tm().transact(() -> tm().getEntityManager().merge(new BsaDownload())); assertThat(persisted.jobId).isNotNull(); - assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.nowUtc()); + assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.now()); assertThat(persisted.stage).isEqualTo(DOWNLOAD_BLOCK_LISTS); } diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java index 5599ad17e..a1a8439e6 100644 --- a/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java @@ -16,19 +16,18 @@ package google.registry.bsa.persistence; import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static org.joda.time.DateTimeZone.UTC; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; import google.registry.testing.FakeClock; -import org.joda.time.DateTime; +import java.time.Instant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link BsaLabel}. */ public class BsaLabelTest { - FakeClock fakeClock = new FakeClock(DateTime.now(UTC)); + FakeClock fakeClock = new FakeClock(Instant.parse("2024-01-01T00:00:00Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = @@ -36,10 +35,10 @@ public class BsaLabelTest { @Test void persist() { - tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc()))); + tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.now()))); BsaLabel persisted = tm().transact(() -> tm().loadByKey(BsaLabel.vKey("label"))); assertThat(persisted.getLabel()).isEqualTo("label"); - assertThat(persisted.creationTime).isEqualTo(fakeClock.nowUtc()); + assertThat(persisted.creationTime).isEqualTo(fakeClock.now()); } @Test @@ -49,7 +48,7 @@ public class BsaLabelTest { @Test void isLabelBlocked_yes() { - tm().transact(() -> tm().put(new BsaLabel("abc", fakeClock.nowUtc()))); + tm().transact(() -> tm().put(new BsaLabel("abc", fakeClock.now()))); assertThat(tm().transact(() -> BsaLabelUtils.isLabelBlocked("abc"))).isTrue(); } } diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaLabelUtilsTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaLabelUtilsTest.java index 74e5f3e6e..bfc387249 100644 --- a/core/src/test/java/google/registry/bsa/persistence/BsaLabelUtilsTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/BsaLabelUtilsTest.java @@ -68,7 +68,7 @@ public class BsaLabelUtilsTest { JpaTransactionManager replicaTm = mock(JpaTransactionManager.class); setJpaTm(() -> primaryTm); setReplicaJpaTm(() -> replicaTm); - when(replicaTm.loadByKey(any())).thenReturn(new BsaLabel("abc", fakeClock.nowUtc())); + when(replicaTm.loadByKey(any())).thenReturn(new BsaLabel("abc", fakeClock.now())); try { assertThat(isLabelBlocked("abc")).isTrue(); assertThat(isLabelBlocked("abc")).isTrue(); @@ -85,7 +85,7 @@ public class BsaLabelUtilsTest { JpaTransactionManager replicaTmSave = replicaTm(); JpaTransactionManager replicaTm = mock(JpaTransactionManager.class); setReplicaJpaTm(() -> replicaTm); - when(replicaTm.loadByKey(any())).thenReturn(new BsaLabel("abc", fakeClock.nowUtc())); + when(replicaTm.loadByKey(any())).thenReturn(new BsaLabel("abc", fakeClock.now())); try { assertThat(isLabelBlocked("abc")).isTrue(); // If test fails, check and fix cache expiry in the config file. Do not increase the duration diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaTestingUtils.java b/core/src/test/java/google/registry/bsa/persistence/BsaTestingUtils.java index eb8d9b22d..5acd3613d 100644 --- a/core/src/test/java/google/registry/bsa/persistence/BsaTestingUtils.java +++ b/core/src/test/java/google/registry/bsa/persistence/BsaTestingUtils.java @@ -21,7 +21,7 @@ import com.google.common.collect.ImmutableList; import google.registry.bsa.DownloadStage; import google.registry.bsa.api.UnblockableDomain; import google.registry.util.Clock; -import org.joda.time.DateTime; +import java.time.Instant; import org.joda.time.Duration; /** Exposes BSA persistence entities and tools to test classes. */ @@ -31,7 +31,7 @@ public final class BsaTestingUtils { public static final Duration DEFAULT_NOP_INTERVAL = Duration.standardDays(1); /** An arbitrary point of time used as BsaLabels' creation time. */ - public static final DateTime BSA_LABEL_CREATION_TIME = DateTime.parse("2023-12-31T00:00:00Z"); + public static final Instant BSA_LABEL_CREATION_TIME = Instant.parse("2023-12-31T00:00:00Z"); private BsaTestingUtils() {} diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaUnblockableDomainTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaUnblockableDomainTest.java index 6c9d3951a..d28f05ea8 100644 --- a/core/src/test/java/google/registry/bsa/persistence/BsaUnblockableDomainTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/BsaUnblockableDomainTest.java @@ -40,7 +40,7 @@ public class BsaUnblockableDomainTest { @Test void persist() { - tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc()))); + tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.now()))); tm().transact(() -> tm().put(new BsaUnblockableDomain("label", "tld", Reason.REGISTERED))); BsaUnblockableDomain persisted = tm().transact(() -> tm().loadByKey(BsaUnblockableDomain.vKey("label", "tld"))); @@ -51,7 +51,7 @@ public class BsaUnblockableDomainTest { @Test void cascadeDeletion() { - tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc()))); + tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.now()))); tm().transact(() -> tm().put(new BsaUnblockableDomain("label", "tld", Reason.REGISTERED))); assertThat( tm().transact(() -> tm().loadByKeyIfPresent(BsaUnblockableDomain.vKey("label", "tld")))) diff --git a/core/src/test/java/google/registry/bsa/persistence/DomainsRefresherTest.java b/core/src/test/java/google/registry/bsa/persistence/DomainsRefresherTest.java index e77b63f02..98efed4ff 100644 --- a/core/src/test/java/google/registry/bsa/persistence/DomainsRefresherTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/DomainsRefresherTest.java @@ -23,7 +23,8 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.newDomain; import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; import com.google.common.collect.ImmutableList; import google.registry.bsa.api.UnblockableDomain; @@ -33,9 +34,9 @@ import google.registry.model.tld.Tld; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; import google.registry.testing.FakeClock; +import java.time.Duration; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; -import org.joda.time.Duration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -43,7 +44,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link DomainsRefresher}. */ public class DomainsRefresherTest { - FakeClock fakeClock = new FakeClock(DateTime.parse("2023-11-09T02:08:57.880Z")); + FakeClock fakeClock = new FakeClock(Instant.parse("2023-11-09T02:08:57.880Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = @@ -57,9 +58,9 @@ public class DomainsRefresherTest { persistResource( Tld.get("tld") .asBuilder() - .setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc().minusMillis(1))) + .setBsaEnrollStartTime(Optional.of(toDateTime(fakeClock.now().minusMillis(1)))) .build()); - refresher = new DomainsRefresher(START_OF_TIME, fakeClock.nowUtc(), Duration.ZERO, 100); + refresher = new DomainsRefresher(START_INSTANT, fakeClock.now(), Duration.ZERO, 100); } @Test diff --git a/core/src/test/java/google/registry/bsa/persistence/DownloadSchedulerTest.java b/core/src/test/java/google/registry/bsa/persistence/DownloadSchedulerTest.java index 564b3b8f9..f29331bc6 100644 --- a/core/src/test/java/google/registry/bsa/persistence/DownloadSchedulerTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/DownloadSchedulerTest.java @@ -22,8 +22,6 @@ import static google.registry.bsa.DownloadStage.MAKE_ORDER_AND_LABEL_DIFF; import static google.registry.bsa.DownloadStage.NOP; import static google.registry.bsa.persistence.DownloadScheduler.fetchTwoMostRecentDownloads; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static org.joda.time.Duration.standardDays; -import static org.joda.time.Duration.standardMinutes; import static org.joda.time.Duration.standardSeconds; import com.google.common.collect.ImmutableMap; @@ -35,8 +33,8 @@ import google.registry.bsa.persistence.DownloadSchedule.CompletedJob; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; import google.registry.testing.FakeClock; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; import org.joda.time.Duration; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -46,10 +44,10 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link DownloadScheduler} */ class DownloadSchedulerTest { - static final Duration DOWNLOAD_INTERVAL = standardMinutes(30); - static final Duration MAX_NOP_INTERVAL = standardDays(1); + static final Duration DOWNLOAD_INTERVAL = Duration.standardMinutes(30); + static final Duration MAX_NOP_INTERVAL = Duration.standardDays(1); - FakeClock fakeClock = new FakeClock(DateTime.parse("2023-11-09T02:08:57.880Z")); + FakeClock fakeClock = new FakeClock(Instant.parse("2023-11-09T02:08:57.880Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = diff --git a/core/src/test/java/google/registry/bsa/persistence/LabelDiffUpdatesTest.java b/core/src/test/java/google/registry/bsa/persistence/LabelDiffUpdatesTest.java index 81477b0e1..570f9a89f 100644 --- a/core/src/test/java/google/registry/bsa/persistence/LabelDiffUpdatesTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/LabelDiffUpdatesTest.java @@ -86,7 +86,7 @@ class LabelDiffUpdatesTest { void applyLabelDiffs_delete() { tm().transact( () -> { - tm().insert(new BsaLabel("label", fakeClock.nowUtc())); + tm().insert(new BsaLabel("label", fakeClock.now())); tm().insert(new BsaUnblockableDomain("label", "app", Reason.REGISTERED)); }); when(idnChecker.getSupportingTlds(any())).thenReturn(ImmutableSet.of(app)); @@ -108,7 +108,7 @@ class LabelDiffUpdatesTest { void applyLabelDiffs_newAssociationOfLabelToOrder() { tm().transact( () -> { - tm().insert(new BsaLabel("label", fakeClock.nowUtc())); + tm().insert(new BsaLabel("label", fakeClock.now())); tm().insert(new BsaUnblockableDomain("label", "app", Reason.REGISTERED)); }); when(idnChecker.getSupportingTlds(any())).thenReturn(ImmutableSet.of(app)); @@ -141,7 +141,7 @@ class LabelDiffUpdatesTest { when(idnChecker.getForbiddingTlds(any())) .thenReturn(Sets.difference(ImmutableSet.of(dev), ImmutableSet.of()).immutableCopy()); when(idnChecker.getSupportingTlds(any())).thenReturn(ImmutableSet.of(app, page)); - when(schedule.jobCreationTime()).thenReturn(fakeClock.nowUtc()); + when(schedule.jobCreationTime()).thenReturn(fakeClock.now()); ImmutableList unblockableDomains = applyLabelDiff( diff --git a/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java b/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java index 0c20a03cb..3e32df5a5 100644 --- a/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java @@ -33,7 +33,8 @@ import static google.registry.testing.DatabaseHelper.newDomain; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted; import static google.registry.testing.DatabaseHelper.persistNewRegistrar; -import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -43,8 +44,8 @@ import google.registry.bsa.persistence.Queries.DomainLifeSpan; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; import google.registry.testing.FakeClock; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -52,7 +53,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link Queries}. */ class QueriesTest { - FakeClock fakeClock = new FakeClock(DateTime.parse("2023-11-09T02:08:57.880Z")); + FakeClock fakeClock = new FakeClock(Instant.parse("2023-11-09T02:08:57.880Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = @@ -64,9 +65,9 @@ class QueriesTest { () -> { tm().putAll( ImmutableList.of( - new BsaLabel("label1", fakeClock.nowUtc()), - new BsaLabel("label2", fakeClock.nowUtc()), - new BsaLabel("label3", fakeClock.nowUtc()))); + new BsaLabel("label1", fakeClock.now()), + new BsaLabel("label2", fakeClock.now()), + new BsaLabel("label3", fakeClock.now()))); tm().putAll( ImmutableList.of( BsaUnblockableDomain.of("label1.app", Reason.REGISTERED), @@ -109,7 +110,7 @@ class QueriesTest { () -> queryBsaLabelByLabels(ImmutableList.of("label1")) .collect(toImmutableList()))) - .containsExactly(new BsaLabel("label1", fakeClock.nowUtc())); + .containsExactly(new BsaLabel("label1", fakeClock.now())); } @Test @@ -120,7 +121,7 @@ class QueriesTest { queryBsaLabelByLabels(ImmutableList.of("label1", "label2")) .collect(toImmutableList()))) .containsExactly( - new BsaLabel("label1", fakeClock.nowUtc()), new BsaLabel("label2", fakeClock.nowUtc())); + new BsaLabel("label1", fakeClock.now()), new BsaLabel("label2", fakeClock.now())); } @Test @@ -139,7 +140,7 @@ class QueriesTest { .isEqualTo(1); assertThat(tm().transact(() -> tm().loadAllOf(BsaLabel.class))) .containsExactly( - new BsaLabel("label2", fakeClock.nowUtc()), new BsaLabel("label3", fakeClock.nowUtc())); + new BsaLabel("label2", fakeClock.now()), new BsaLabel("label3", fakeClock.now())); assertThat( tm().transact( () -> @@ -156,7 +157,7 @@ class QueriesTest { assertThat(tm().transact(() -> deleteBsaLabelByLabels(ImmutableList.of("label1", "label2")))) .isEqualTo(2); assertThat(tm().transact(() -> tm().loadAllOf(BsaLabel.class))) - .containsExactly(new BsaLabel("label3", fakeClock.nowUtc())); + .containsExactly(new BsaLabel("label3", fakeClock.now())); assertThat( tm().transact( () -> @@ -171,8 +172,8 @@ class QueriesTest { () -> tm().insertAll( ImmutableList.of( - new BsaLabel("a", fakeClock.nowUtc()), - new BsaLabel("b", fakeClock.nowUtc())))); + new BsaLabel("a", fakeClock.now()), + new BsaLabel("b", fakeClock.now())))); BsaUnblockableDomain a1 = new BsaUnblockableDomain("a", "tld1", Reason.RESERVED); BsaUnblockableDomain b1 = new BsaUnblockableDomain("b", "tld1", Reason.REGISTERED); BsaUnblockableDomain a2 = new BsaUnblockableDomain("a", "tld2", Reason.REGISTERED); @@ -203,65 +204,65 @@ class QueriesTest { @Test void queryNewlyCreatedDomains_onlyLiveDomainsReturned() { - DateTime testStartTime = fakeClock.nowUtc(); + Instant testStartTime = fakeClock.now(); createTlds("tld"); persistNewRegistrar("TheRegistrar"); // time 0: - persistActiveDomain("d1.tld", fakeClock.nowUtc()); + persistActiveDomain("d1.tld", toDateTime(fakeClock.now())); // time 0, deletion time 1 persistDomainAsDeleted( - newDomain("will-delete.tld").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build(), - fakeClock.nowUtc().plusMillis(1)); + newDomain("will-delete.tld").asBuilder().setCreationTimeForTest(fakeClock.now()).build(), + toDateTime(fakeClock.now().plusMillis(1))); fakeClock.advanceOneMilli(); // time 1 - persistActiveDomain("d2.tld", fakeClock.nowUtc()); + persistActiveDomain("d2.tld", toDateTime(fakeClock.now())); fakeClock.advanceOneMilli(); // Now is time 2 assertThat( bsaQuery( () -> queryNewlyCreatedDomains( - ImmutableList.of("tld"), testStartTime, fakeClock.nowUtc()))) + ImmutableList.of("tld"), testStartTime, fakeClock.now()))) .containsExactly("d1.tld", "d2.tld"); } @Test void queryNewlyCreatedDomains_onlyDomainsAfterMinCreationTimeReturned() { - DateTime testStartTime = fakeClock.nowUtc(); + Instant testStartTime = fakeClock.now(); createTlds("tld"); persistNewRegistrar("TheRegistrar"); // time 0: - persistActiveDomain("d1.tld", fakeClock.nowUtc()); + persistActiveDomain("d1.tld", toDateTime(fakeClock.now())); // time 0, deletion time 1 persistDomainAsDeleted( - newDomain("will-delete.tld").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build(), - fakeClock.nowUtc().plusMillis(1)); + newDomain("will-delete.tld").asBuilder().setCreationTimeForTest(fakeClock.now()).build(), + toDateTime(fakeClock.now().plusMillis(1))); fakeClock.advanceOneMilli(); // time 1 - persistActiveDomain("d2.tld", fakeClock.nowUtc()); + persistActiveDomain("d2.tld", toDateTime(fakeClock.now())); fakeClock.advanceOneMilli(); // Now is time 2, ask for domains created since time 1 assertThat( bsaQuery( () -> queryNewlyCreatedDomains( - ImmutableList.of("tld"), testStartTime.plusMillis(1), fakeClock.nowUtc()))) + ImmutableList.of("tld"), testStartTime.plusMillis(1), fakeClock.now()))) .containsExactly("d2.tld"); } @Test void queryNewlyCreatedDomains_onlyDomainsInRequestedTldsReturned() { - DateTime testStartTime = fakeClock.nowUtc(); + Instant testStartTime = fakeClock.now(); createTlds("tld", "tld2"); persistNewRegistrar("TheRegistrar"); - persistActiveDomain("d1.tld", fakeClock.nowUtc()); - persistActiveDomain("d2.tld2", fakeClock.nowUtc()); + persistActiveDomain("d1.tld", toDateTime(fakeClock.now())); + persistActiveDomain("d2.tld2", toDateTime(fakeClock.now())); fakeClock.advanceOneMilli(); assertThat( bsaQuery( () -> queryNewlyCreatedDomains( - ImmutableList.of("tld"), testStartTime, fakeClock.nowUtc()))) + ImmutableList.of("tld"), testStartTime, fakeClock.now()))) .containsExactly("d1.tld"); } @@ -269,34 +270,41 @@ class QueriesTest { void queryMissedRegisteredUnblockables_success() { createTlds("tld", "tld2"); persistNewRegistrar("TheRegistrar"); - DateTime time1 = fakeClock.nowUtc(); - persistActiveDomain("unblocked1.tld", fakeClock.nowUtc()); - persistActiveDomain("unblocked2.tld2", fakeClock.nowUtc()); - persistActiveDomain("label1.tld", fakeClock.nowUtc()); - persistActiveDomain("label2.tld2", fakeClock.nowUtc()); + Instant time1 = fakeClock.now(); + persistActiveDomain("unblocked1.tld", toDateTime(fakeClock.now())); + persistActiveDomain("unblocked2.tld2", toDateTime(fakeClock.now())); + persistActiveDomain("label1.tld", toDateTime(fakeClock.now())); + persistActiveDomain("label2.tld2", toDateTime(fakeClock.now())); fakeClock.advanceOneMilli(); - DateTime time2 = fakeClock.nowUtc(); + Instant time2 = fakeClock.now(); persistDomainAsDeleted( - newDomain("label3.tld").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build(), - fakeClock.nowUtc().plusMillis(1)); + newDomain("label3.tld").asBuilder().setCreationTimeForTest(fakeClock.now()).build(), + toDateTime(fakeClock.now().plusMillis(1))); // Deleted in the future persistDomainAsDeleted( - newDomain("label3.tld2").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build(), - fakeClock.nowUtc().plusHours(1)); + newDomain("label3.tld2").asBuilder().setCreationTimeForTest(fakeClock.now()).build(), + toDateTime(fakeClock.now().plus(java.time.Duration.ofHours(1)))); fakeClock.advanceOneMilli(); - assertThat(bsaQuery(() -> queryMissedRegisteredUnblockables("tld", fakeClock.nowUtc()))) - .containsExactly(new DomainLifeSpan("label1.tld", time1, END_OF_TIME)); - assertThat(bsaQuery(() -> queryMissedRegisteredUnblockables("tld2", fakeClock.nowUtc()))) + assertThat( + (ImmutableList) + bsaQuery(() -> queryMissedRegisteredUnblockables("tld", fakeClock.now()))) + .containsExactly(new DomainLifeSpan("label1.tld", time1, END_INSTANT)); + assertThat( + (ImmutableList) + bsaQuery(() -> queryMissedRegisteredUnblockables("tld2", fakeClock.now()))) .containsExactly( - new DomainLifeSpan("label2.tld2", time1, END_OF_TIME), - new DomainLifeSpan("label3.tld2", time2, time2.plusHours(1))); + new DomainLifeSpan("label2.tld2", time1, END_INSTANT), + new DomainLifeSpan("label3.tld2", time2, time2.plus(java.time.Duration.ofHours(1)))); BsaTestingUtils.persistUnblockableDomain( UnblockableDomain.of("label2", "tld2", UnblockableDomain.Reason.REGISTERED)); BsaTestingUtils.persistUnblockableDomain( UnblockableDomain.of("label3", "tld2", UnblockableDomain.Reason.RESERVED)); - assertThat(bsaQuery(() -> queryMissedRegisteredUnblockables("tld2", fakeClock.nowUtc()))) - .containsExactly(new DomainLifeSpan("label3.tld2", time2, time2.plusHours(1))); + assertThat( + (ImmutableList) + bsaQuery(() -> queryMissedRegisteredUnblockables("tld2", fakeClock.now()))) + .containsExactly( + new DomainLifeSpan("label3.tld2", time2, time2.plus(java.time.Duration.ofHours(1)))); } @Test diff --git a/core/src/test/java/google/registry/bsa/persistence/RefreshSchedulerTest.java b/core/src/test/java/google/registry/bsa/persistence/RefreshSchedulerTest.java index 13dec9217..a930c86c7 100644 --- a/core/src/test/java/google/registry/bsa/persistence/RefreshSchedulerTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/RefreshSchedulerTest.java @@ -24,8 +24,8 @@ import google.registry.bsa.RefreshStage; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; import google.registry.testing.FakeClock; +import java.time.Instant; import java.util.Optional; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -33,7 +33,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link RefreshScheduler}. */ public class RefreshSchedulerTest { - FakeClock fakeClock = new FakeClock(DateTime.parse("2023-11-09T02:08:57.880Z")); + FakeClock fakeClock = new FakeClock(Instant.parse("2023-11-09T02:08:57.880Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = @@ -62,14 +62,14 @@ public class RefreshSchedulerTest { @Test void schedule_NoPreviousRefresh_withCompletedPrevDownload() { tm().transact(() -> tm().insert(new BsaDownload().setStage(DownloadStage.DONE))); - DateTime downloadTime = fakeClock.nowUtc(); + Instant downloadTime = fakeClock.now(); fakeClock.advanceOneMilli(); Optional scheduleOptional = scheduler.schedule(); assertThat(scheduleOptional).isPresent(); RefreshSchedule schedule = scheduleOptional.get(); - assertThat(schedule.jobCreationTime()).isEqualTo(fakeClock.nowUtc()); + assertThat(schedule.jobCreationTime()).isEqualTo(fakeClock.now()); assertThat(schedule.stage()).isEqualTo(RefreshStage.CHECK_FOR_CHANGES); assertThat(schedule.prevRefreshTime()).isEqualTo(downloadTime); } @@ -77,11 +77,11 @@ public class RefreshSchedulerTest { @Test void schedule_firstRefreshOngoing() { tm().transact(() -> tm().insert(new BsaDownload().setStage(DownloadStage.DONE))); - DateTime downloadTime = fakeClock.nowUtc(); + Instant downloadTime = fakeClock.now(); fakeClock.advanceOneMilli(); tm().transact(() -> tm().insert(new BsaDomainRefresh().setStage(APPLY_CHANGES))); - DateTime refreshStartTime = fakeClock.nowUtc(); + Instant refreshStartTime = fakeClock.now(); fakeClock.advanceOneMilli(); Optional scheduleOptional = scheduler.schedule(); @@ -96,14 +96,14 @@ public class RefreshSchedulerTest { @Test void schedule_firstRefreshDone() { tm().transact(() -> tm().insert(new BsaDomainRefresh().setStage(DONE))); - DateTime prevRefreshStartTime = fakeClock.nowUtc(); + Instant prevRefreshStartTime = fakeClock.now(); fakeClock.advanceOneMilli(); Optional scheduleOptional = scheduler.schedule(); assertThat(scheduleOptional).isPresent(); RefreshSchedule schedule = scheduleOptional.get(); - assertThat(schedule.jobCreationTime()).isEqualTo(fakeClock.nowUtc()); + assertThat(schedule.jobCreationTime()).isEqualTo(fakeClock.now()); assertThat(schedule.stage()).isEqualTo(RefreshStage.CHECK_FOR_CHANGES); assertThat(schedule.prevRefreshTime()).isEqualTo(prevRefreshStartTime); } @@ -111,10 +111,10 @@ public class RefreshSchedulerTest { @Test void schedule_ongoingRefreshWithPrevCompletion() { tm().transact(() -> tm().insert(new BsaDomainRefresh().setStage(DONE))); - DateTime prevRefreshStartTime = fakeClock.nowUtc(); + Instant prevRefreshStartTime = fakeClock.now(); fakeClock.advanceOneMilli(); tm().transact(() -> tm().insert(new BsaDomainRefresh().setStage(APPLY_CHANGES))); - DateTime ongoingRefreshStartTime = fakeClock.nowUtc(); + Instant ongoingRefreshStartTime = fakeClock.now(); fakeClock.advanceOneMilli(); Optional scheduleOptional = scheduler.schedule(); diff --git a/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java b/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java index 49333048d..1d375dd2b 100644 --- a/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java +++ b/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java @@ -42,6 +42,8 @@ import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.testing.DatabaseHelper; import google.registry.testing.FakeClock; +import java.time.Duration; +import java.time.Instant; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -88,9 +90,11 @@ public class SyncRegistrarsSheetTest { @Test void test_wereRegistrarsModified_atDifferentCursorTimes() { persistNewRegistrar("SomeRegistrar", "Some Registrar Inc.", Registrar.Type.REAL, 8L); - persistResource(Cursor.createGlobal(SYNC_REGISTRAR_SHEET, clock.nowUtc().minusHours(1))); + persistResource( + Cursor.createGlobal(SYNC_REGISTRAR_SHEET, clock.now().minus(Duration.ofHours(1)))); assertThat(newSyncRegistrarsSheet().wereRegistrarsModified()).isTrue(); - persistResource(Cursor.createGlobal(SYNC_REGISTRAR_SHEET, clock.nowUtc().plusHours(1))); + persistResource( + Cursor.createGlobal(SYNC_REGISTRAR_SHEET, clock.now().plus(Duration.ofHours(1)))); assertThat(newSyncRegistrarsSheet().wereRegistrarsModified()).isFalse(); } @@ -178,7 +182,7 @@ public class SyncRegistrarsSheetTest { .setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH)) .build()); // Use registrar key for contacts' parent. - DateTime registrarCreationTime = persistResource(registrar).getCreationTime(); + Instant registrarCreationTime = persistResource(registrar).getCreationTime(); persistResources(contacts); clock.advanceBy(standardMinutes(1)); @@ -315,7 +319,7 @@ public class SyncRegistrarsSheetTest { Cursor cursor = loadByKey(Cursor.createGlobalVKey(SYNC_REGISTRAR_SHEET)); assertThat(cursor).isNotNull(); - assertThat(cursor.getCursorTime()).isGreaterThan(registrarCreationTime); + assertThat(cursor.getCursorTimeInstant()).isGreaterThan(registrarCreationTime); } @Test diff --git a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java index 6c402def6..afb8dbe0c 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java @@ -179,6 +179,7 @@ import google.registry.tmch.TmchTestData; import google.registry.xml.ValidationMode; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; @@ -325,10 +326,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase autorenewVKey = domain.getAutorenewBillingEvent(); + BillingRecurrence autorenewBR = tm().transact(() -> tm().loadByKey(autorenewVKey)); + Instant eventTime = autorenewBR.getEventTimeInstant(); assertAboutDomains() .that(domain) - .hasRegistrationExpirationTime( - tm().transact(() -> tm().loadByKey(domain.getAutorenewBillingEvent()).getEventTime())) + .hasRegistrationExpirationTime(eventTime) .and() .hasOnlyOneHistoryEntryWhich() .hasType(HistoryEntry.Type.DOMAIN_CREATE) diff --git a/core/src/test/java/google/registry/flows/domain/DomainInfoFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainInfoFlowTest.java index 9d5977005..4e68647e4 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainInfoFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainInfoFlowTest.java @@ -73,6 +73,7 @@ import google.registry.persistence.VKey; import google.registry.persistence.transaction.JpaTransactionManagerExtension; import google.registry.testing.DatabaseHelper; import google.registry.xml.ValidationMode; +import java.time.Instant; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.joda.money.Money; @@ -336,8 +337,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase { .setCreationRegistrarId("NewRegistrar") .setCreationTimeForTest(DateTime.parse("2003-11-26T22:00:00.0Z")) .setRegistrationExpirationTime(DateTime.parse("2005-11-26T22:00:00.0Z")) - .setLastTransferTime(null) - .setLastEppUpdateTime(null) + .setLastTransferTime((Instant) null) + .setLastEppUpdateTime((Instant) null) .setLastEppUpdateRegistrarId(null) .build()); doSuccessfulTest("domain_info_response_addperiod.xml", false); diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java index 9e2adb869..f8a92a82b 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainTransferApproveFlowTest.java @@ -241,7 +241,7 @@ class DomainTransferApproveFlowTest .collect(onlyElement()); assertThat(transferResponse.getTransferStatus()).isEqualTo(TransferStatus.CLIENT_APPROVED); assertThat(transferResponse.getExtendedRegistrationExpirationTime()) - .isEqualTo(domain.getRegistrationExpirationDateTime()); + .isEqualTo(domain.getRegistrationExpirationTime()); PendingActionNotificationResponse panData = gainingTransferPollMessage .getResponseData() diff --git a/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java b/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java index 2103b06d4..c7e1ea7e5 100644 --- a/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostUpdateFlowTest.java @@ -81,6 +81,7 @@ import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferStatus; import google.registry.testing.CloudTasksHelper.TaskMatcher; import google.registry.testing.DatabaseHelper; +import java.time.Instant; import javax.annotation.Nullable; import org.joda.time.DateTime; import org.junit.jupiter.api.Test; @@ -523,7 +524,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase { newHost(oldHostName()) .asBuilder() .setSuperordinateDomain(foo.createVKey()) - .setLastTransferTime(null) + .setLastTransferTime((Instant) null) .setInetAddresses( ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A"))) .build()); @@ -595,7 +596,10 @@ class HostUpdateFlowTest extends ResourceFlowTestCase { .build()); // Set the new domain to have a null last transfer time. persistResource( - DatabaseHelper.newDomain("example.tld").asBuilder().setLastTransferTime(null).build()); + DatabaseHelper.newDomain("example.tld") + .asBuilder() + .setLastTransferTime((Instant) null) + .build()); DateTime lastTransferTime = clock.nowUtc().minusDays(20); persistResource( @@ -628,10 +632,16 @@ class HostUpdateFlowTest extends ResourceFlowTestCase { createTld("tld"); Domain foo = persistResource( - DatabaseHelper.newDomain("foo.tld").asBuilder().setLastTransferTime(null).build()); + DatabaseHelper.newDomain("foo.tld") + .asBuilder() + .setLastTransferTime((Instant) null) + .build()); // Set the new domain to have a null last transfer time. persistResource( - DatabaseHelper.newDomain("example.tld").asBuilder().setLastTransferTime(null).build()); + DatabaseHelper.newDomain("example.tld") + .asBuilder() + .setLastTransferTime((Instant) null) + .build()); DateTime lastTransferTime = clock.nowUtc().minusDays(20); persistResource( @@ -669,12 +679,15 @@ class HostUpdateFlowTest extends ResourceFlowTestCase { .build()); // Set the new domain to have a null last transfer time. persistResource( - DatabaseHelper.newDomain("example.tld").asBuilder().setLastTransferTime(null).build()); + DatabaseHelper.newDomain("example.tld") + .asBuilder() + .setLastTransferTime((Instant) null) + .build()); persistResource( newHost(oldHostName()) .asBuilder() .setSuperordinateDomain(foo.createVKey()) - .setLastTransferTime(null) + .setLastTransferTime((Instant) null) .setLastSuperordinateChange(clock.nowUtc().minusDays(3)) .setInetAddresses( ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A"))) diff --git a/core/src/test/java/google/registry/model/CreateAutoTimestampTest.java b/core/src/test/java/google/registry/model/CreateAutoTimestampTest.java index 5029d1d7e..9f7dd5dff 100644 --- a/core/src/test/java/google/registry/model/CreateAutoTimestampTest.java +++ b/core/src/test/java/google/registry/model/CreateAutoTimestampTest.java @@ -17,23 +17,27 @@ package google.registry.model; import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.loadByEntity; -import static org.joda.time.DateTimeZone.UTC; import google.registry.model.common.CrossTldSingleton; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension; +import google.registry.testing.FakeClock; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.joda.time.DateTime; +import java.time.Duration; +import java.time.Instant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link CreateAutoTimestamp}. */ public class CreateAutoTimestampTest { + private final FakeClock clock = new FakeClock(Instant.parse("2024-01-01T00:00:00Z")); + @RegisterExtension public final JpaUnitTestExtension jpaUnitTestExtension = new JpaTestExtensions.Builder() + .withClock(clock) .withEntityClass(CreateAutoTimestampTestObject.class) .buildUnitTestExtension(); @@ -41,7 +45,7 @@ public class CreateAutoTimestampTest { @Entity public static class CreateAutoTimestampTestObject extends CrossTldSingleton { @Id long id = SINGLETON_ID; - CreateAutoTimestamp createTime = CreateAutoTimestamp.create(null); + CreateAutoTimestamp createTime = CreateAutoTimestamp.create((Instant) null); } private static CreateAutoTimestampTestObject reload() { @@ -50,20 +54,20 @@ public class CreateAutoTimestampTest { @Test void testSaveSetsTime() { - DateTime transactionTime = + Instant transactionTime = tm().transact( () -> { CreateAutoTimestampTestObject object = new CreateAutoTimestampTestObject(); assertThat(object.createTime.getTimestamp()).isNull(); tm().put(object); - return tm().getTransactionTime(); + return tm().getTxTime(); }); assertThat(reload().createTime.getTimestamp()).isEqualTo(transactionTime); } @Test void testResavingRespectsOriginalTime() { - final DateTime oldCreateTime = DateTime.now(UTC).minusDays(1); + final Instant oldCreateTime = clock.now().minus(Duration.ofDays(1)); tm().transact( () -> { CreateAutoTimestampTestObject object = new CreateAutoTimestampTestObject(); diff --git a/core/src/test/java/google/registry/model/UpdateAutoTimestampTest.java b/core/src/test/java/google/registry/model/UpdateAutoTimestampTest.java index a61eb2398..3fc66e209 100644 --- a/core/src/test/java/google/registry/model/UpdateAutoTimestampTest.java +++ b/core/src/test/java/google/registry/model/UpdateAutoTimestampTest.java @@ -16,8 +16,7 @@ package google.registry.model; import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.DateTimeUtils.START_OF_TIME; -import static org.joda.time.DateTimeZone.UTC; +import static google.registry.util.DateTimeUtils.START_INSTANT; import google.registry.model.common.CrossTldSingleton; import google.registry.persistence.VKey; @@ -26,7 +25,8 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExte import google.registry.testing.FakeClock; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.joda.time.DateTime; +import java.time.Duration; +import java.time.Instant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -46,7 +46,7 @@ public class UpdateAutoTimestampTest { @Entity public static class UpdateAutoTimestampTestObject extends CrossTldSingleton { @Id long id = SINGLETON_ID; - UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null); + UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create((Instant) null); } private static UpdateAutoTimestampTestObject reload() { @@ -56,35 +56,36 @@ public class UpdateAutoTimestampTest { @Test void testSaveSetsTime() { - DateTime transactionTime = + Instant transactionTime = tm().transact( () -> { clock.advanceOneMilli(); UpdateAutoTimestampTestObject object = new UpdateAutoTimestampTestObject(); - assertThat(object.updateTime.getTimestamp()).isEqualTo(START_OF_TIME); + assertThat(object.updateTime.getTimestamp()).isEqualTo(START_INSTANT); tm().insert(object); - return tm().getTransactionTime(); + return tm().getTxTime(); }); assertThat(reload().updateTime.getTimestamp()).isEqualTo(transactionTime); } @Test void testResavingOverwritesOriginalTime() { - DateTime transactionTime = + Instant transactionTime = tm().transact( () -> { clock.advanceOneMilli(); UpdateAutoTimestampTestObject object = new UpdateAutoTimestampTestObject(); - object.updateTime = UpdateAutoTimestamp.create(DateTime.now(UTC).minusDays(1)); + object.updateTime = + UpdateAutoTimestamp.create(clock.now().minus(Duration.ofDays(1))); tm().insert(object); - return tm().getTransactionTime(); + return tm().getTxTime(); }); assertThat(reload().updateTime.getTimestamp()).isEqualTo(transactionTime); } @Test void testReadingTwiceDoesNotModify() { - DateTime originalTime = DateTime.parse("1999-01-01T00:00:00Z"); + Instant originalTime = Instant.parse("1999-01-01T00:00:00Z"); clock.setTo(originalTime); tm().transact(() -> tm().insert(new UpdateAutoTimestampTestObject())); clock.advanceOneMilli(); 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 1c041fefe..b1f20747e 100644 --- a/core/src/test/java/google/registry/model/common/CursorTest.java +++ b/core/src/test/java/google/registry/model/common/CursorTest.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import google.registry.model.EntityTestCase; import google.registry.model.tld.Tld; import google.registry.util.SerializeUtils; +import java.time.Instant; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -116,7 +117,7 @@ public class CursorTest extends EntityTestCase { NullPointerException thrown = assertThrows( NullPointerException.class, - () -> Cursor.createScoped(RDE_UPLOAD, null, Tld.get("tld"))); + () -> Cursor.createScoped(RDE_UPLOAD, (Instant) null, Tld.get("tld"))); assertThat(thrown).hasMessageThat().contains("Cursor time cannot be null"); } } diff --git a/core/src/test/java/google/registry/model/console/PasswordResetRequestTest.java b/core/src/test/java/google/registry/model/console/PasswordResetRequestTest.java index d46ed1fc9..9b6b16082 100644 --- a/core/src/test/java/google/registry/model/console/PasswordResetRequestTest.java +++ b/core/src/test/java/google/registry/model/console/PasswordResetRequestTest.java @@ -46,7 +46,7 @@ public class PasswordResetRequestTest extends EntityTestCase { PasswordResetRequest fromDatabase = DatabaseHelper.loadByKey(VKey.create(PasswordResetRequest.class, verificationCode)); assertAboutImmutableObjects().that(fromDatabase).isEqualExceptFields(request, "requestTime"); - assertThat(fromDatabase.getRequestTime()).isEqualTo(fakeClock.nowUtc()); + assertThat(fromDatabase.getRequestTime()).isEqualTo(fakeClock.now()); } @Test diff --git a/core/src/test/java/google/registry/model/domain/DomainSqlTest.java b/core/src/test/java/google/registry/model/domain/DomainSqlTest.java index 7b1e94d27..0c26a5230 100644 --- a/core/src/test/java/google/registry/model/domain/DomainSqlTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainSqlTest.java @@ -26,9 +26,9 @@ import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.DatabaseHelper.persistResources; import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation; import static google.registry.testing.SqlHelper.saveRegistrar; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.START_OF_TIME; -import static org.joda.time.DateTimeZone.UTC; +import static google.registry.util.DateTimeUtils.END_INSTANT; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.plusYears; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -49,10 +49,10 @@ import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; import google.registry.testing.FakeClock; import google.registry.util.SerializeUtils; +import java.time.Instant; import java.util.Arrays; import org.joda.money.CurrencyUnit; import org.joda.money.Money; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -60,7 +60,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Verify that we can store/retrieve Domain objects from a SQL database. */ public class DomainSqlTest { - protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC)); + protected FakeClock fakeClock = new FakeClock(Instant.parse("2024-01-01T00:00:00Z")); @RegisterExtension final JpaIntegrationWithCoverageExtension jpa = @@ -83,9 +83,9 @@ public class DomainSqlTest { .setDomainName("example.com") .setRepoId("4-COM") .setCreationRegistrarId("registrar1") - .setLastEppUpdateTime(fakeClock.nowUtc()) + .setLastEppUpdateTime(fakeClock.now()) .setLastEppUpdateRegistrarId("registrar2") - .setLastTransferTime(fakeClock.nowUtc()) + .setLastTransferTime(fakeClock.now()) .setNameservers(host1VKey) .setStatusValues( ImmutableSet.of( @@ -97,15 +97,15 @@ public class DomainSqlTest { StatusValue.SERVER_HOLD)) .setSubordinateHosts(ImmutableSet.of("ns1.example.com")) .setPersistedCurrentSponsorRegistrarId("registrar3") - .setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1)) + .setRegistrationExpirationTime(plusYears(fakeClock.now(), 1)) .setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password"))) .setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2}))) .setLaunchNotice( - LaunchNotice.create("tcnid", "validatorId", START_OF_TIME, START_OF_TIME)) + LaunchNotice.create("tcnid", "validatorId", START_INSTANT, START_INSTANT)) .setSmdId("smdid") .addGracePeriod( GracePeriod.create( - GracePeriodStatus.ADD, "4-COM", END_OF_TIME, "registrar1", null, 100L)) + GracePeriodStatus.ADD, "4-COM", END_INSTANT, "registrar1", null, 100L)) .build(); host = @@ -121,17 +121,17 @@ public class DomainSqlTest { .setToken("abc123Unlimited") .setTokenType(BULK_PRICING) .setDiscountFraction(1.0) - .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(Instant.parse("2010-11-12T05:00:00Z")) .setAllowedTlds(ImmutableSet.of("dev", "app")) .setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar")) .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) .setRenewalPrice(Money.of(CurrencyUnit.USD, 0)) .setAllowedEppActions(ImmutableSet.of(CommandName.CREATE)) - .setTokenStatusTransitions( - ImmutableSortedMap.naturalOrder() - .put(START_OF_TIME, NOT_STARTED) - .put(DateTime.now(UTC), TokenStatus.VALID) - .put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED) + .setTokenStatusTransitionsInstant( + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), TokenStatus.VALID) + .put(fakeClock.now().plus(java.time.Duration.ofDays(56)), TokenStatus.ENDED) .build()) .build(); } @@ -221,7 +221,7 @@ public class DomainSqlTest { GracePeriod.create( GracePeriodStatus.RENEW, "4-COM", - END_OF_TIME, + END_INSTANT, "registrar1", null, 200L)) @@ -235,9 +235,9 @@ public class DomainSqlTest { assertThat(persisted.getGracePeriods()) .containsExactly( GracePeriod.create( - GracePeriodStatus.ADD, "4-COM", END_OF_TIME, "registrar1", null, 100L), + GracePeriodStatus.ADD, "4-COM", END_INSTANT, "registrar1", null, 100L), GracePeriod.create( - GracePeriodStatus.RENEW, "4-COM", END_OF_TIME, "registrar1", null, 200L)); + GracePeriodStatus.RENEW, "4-COM", END_INSTANT, "registrar1", null, 200L)); assertEqualDomainExcept(persisted, "gracePeriods"); }); @@ -281,7 +281,7 @@ public class DomainSqlTest { GracePeriod.create( GracePeriodStatus.ADD, "4-COM", - END_OF_TIME, + END_INSTANT, "registrar1", null, 100L)) @@ -295,7 +295,7 @@ public class DomainSqlTest { assertThat(persisted.getGracePeriods()) .containsExactly( GracePeriod.create( - GracePeriodStatus.ADD, "4-COM", END_OF_TIME, "registrar1", null, 100L)); + GracePeriodStatus.ADD, "4-COM", END_INSTANT, "registrar1", null, 100L)); assertEqualDomainExcept(persisted, "gracePeriods"); }); } @@ -387,7 +387,7 @@ public class DomainSqlTest { void testUpdateTimeAfterNameserverUpdate() { persistDomain(); Domain persisted = loadByKey(domain.createVKey()); - DateTime originalUpdateTime = persisted.getUpdateTimestamp().getTimestamp(); + Instant originalUpdateTime = persisted.getUpdateTimestamp().getTimestamp(); fakeClock.advanceOneMilli(); Host host2 = new Host.Builder() @@ -408,7 +408,7 @@ public class DomainSqlTest { void testUpdateTimeAfterDsDataUpdate() { persistDomain(); Domain persisted = loadByKey(domain.createVKey()); - DateTime originalUpdateTime = persisted.getUpdateTimestamp().getTimestamp(); + Instant originalUpdateTime = persisted.getUpdateTimestamp().getTimestamp(); fakeClock.advanceOneMilli(); domain = persisted diff --git a/core/src/test/java/google/registry/model/domain/DomainTest.java b/core/src/test/java/google/registry/model/domain/DomainTest.java index ad04eb1d0..5c2919f36 100644 --- a/core/src/test/java/google/registry/model/domain/DomainTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainTest.java @@ -547,7 +547,7 @@ public class DomainTest { .setRegistrationExpirationTime(oldExpirationTime) .setTransferData(DomainTransferData.EMPTY) .setGracePeriods(ImmutableSet.of()) - .setLastEppUpdateTime(null) + .setLastEppUpdateTime((Instant) null) .setLastEppUpdateRegistrarId(null) .build(); } diff --git a/core/src/test/java/google/registry/model/domain/GracePeriodTest.java b/core/src/test/java/google/registry/model/domain/GracePeriodTest.java index 8453d28dd..1b3a17a5f 100644 --- a/core/src/test/java/google/registry/model/domain/GracePeriodTest.java +++ b/core/src/test/java/google/registry/model/domain/GracePeriodTest.java @@ -15,7 +15,6 @@ package google.registry.model.domain; import static com.google.common.truth.Truth.assertThat; -import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.Assertions.assertThrows; import google.registry.model.billing.BillingBase.Reason; @@ -26,9 +25,11 @@ import google.registry.model.reporting.HistoryEntry.HistoryEntryId; import google.registry.persistence.VKey; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; +import google.registry.testing.FakeClock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import org.joda.money.CurrencyUnit; import org.joda.money.Money; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -36,11 +37,13 @@ import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link GracePeriod}. */ public class GracePeriodTest { + private final FakeClock fakeClock = new FakeClock(Instant.parse("2024-01-01T00:00:00Z")); + @RegisterExtension final JpaIntegrationTestExtension jpa = - new JpaTestExtensions.Builder().buildIntegrationTestExtension(); + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension(); - private final DateTime now = DateTime.now(UTC); + private final Instant now = fakeClock.now(); private BillingEvent onetime; private VKey recurrenceKey; @@ -49,7 +52,7 @@ public class GracePeriodTest { onetime = new BillingEvent.Builder() .setEventTime(now) - .setBillingTime(now.plusDays(1)) + .setBillingTime(now.plus(1, ChronoUnit.DAYS)) .setRegistrarId("TheRegistrar") .setCost(Money.of(CurrencyUnit.USD, 42)) .setDomainHistoryId(new HistoryEntryId("domain", 12345)) @@ -68,7 +71,7 @@ public class GracePeriodTest { assertThat(gracePeriod.getBillingEvent()).isEqualTo(onetime.createVKey()); assertThat(gracePeriod.getBillingRecurrence()).isNull(); assertThat(gracePeriod.getRegistrarId()).isEqualTo("TheRegistrar"); - assertThat(gracePeriod.getExpirationDateTime()).isEqualTo(now.plusDays(1)); + assertThat(gracePeriod.getExpirationTime()).isEqualTo(now.plus(1, ChronoUnit.DAYS)); assertThat(gracePeriod.hasBillingEvent()).isTrue(); } @@ -76,13 +79,17 @@ public class GracePeriodTest { void testSuccess_forRecurrence() { GracePeriod gracePeriod = GracePeriod.createForRecurrence( - GracePeriodStatus.AUTO_RENEW, "1-TEST", now.plusDays(1), "TheRegistrar", recurrenceKey); + GracePeriodStatus.AUTO_RENEW, + "1-TEST", + now.plus(1, ChronoUnit.DAYS), + "TheRegistrar", + recurrenceKey); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.AUTO_RENEW); assertThat(gracePeriod.getDomainRepoId()).isEqualTo("1-TEST"); assertThat(gracePeriod.getBillingEvent()).isNull(); assertThat(gracePeriod.getBillingRecurrence()).isEqualTo(recurrenceKey); assertThat(gracePeriod.getRegistrarId()).isEqualTo("TheRegistrar"); - assertThat(gracePeriod.getExpirationDateTime()).isEqualTo(now.plusDays(1)); + assertThat(gracePeriod.getExpirationTime()).isEqualTo(now.plus(1, ChronoUnit.DAYS)); assertThat(gracePeriod.hasBillingEvent()).isTrue(); } @@ -96,7 +103,7 @@ public class GracePeriodTest { assertThat(gracePeriod.getBillingEvent()).isNull(); assertThat(gracePeriod.getBillingRecurrence()).isNull(); assertThat(gracePeriod.getRegistrarId()).isEqualTo("TheRegistrar"); - assertThat(gracePeriod.getExpirationDateTime()).isEqualTo(now); + assertThat(gracePeriod.getExpirationTime()).isEqualTo(now); assertThat(gracePeriod.hasBillingEvent()).isFalse(); } @@ -118,7 +125,7 @@ public class GracePeriodTest { GracePeriod.createForRecurrence( GracePeriodStatus.RENEW, "1-TEST", - now.plusDays(1), + now.plus(1, ChronoUnit.DAYS), "TheRegistrar", recurrenceKey)); assertThat(thrown).hasMessageThat().contains("autorenew"); 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 6fbf1962f..9399dfd2c 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 @@ -27,8 +27,7 @@ import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.loadByEntity; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.util.DateTimeUtils.START_OF_TIME; -import static org.joda.time.DateTimeZone.UTC; +import static google.registry.util.DateTimeUtils.START_INSTANT; import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableSet; @@ -43,9 +42,9 @@ import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.reporting.HistoryEntry.HistoryEntryId; import google.registry.util.SerializeUtils; +import java.time.Instant; import org.joda.money.CurrencyUnit; import org.joda.money.Money; -import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -68,17 +67,17 @@ public class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder() .setToken("abc123Unlimited") .setTokenType(UNLIMITED_USE) - .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(Instant.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) + .setTokenStatusTransitionsInstant( + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), TokenStatus.VALID) + .put(fakeClock.now().plus(java.time.Duration.ofDays(56)), TokenStatus.ENDED) .build()) .setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.RENEW)) .build()); @@ -92,7 +91,7 @@ public class AllocationTokenTest extends EntityTestCase { .setToken("abc123Single") .setRedemptionHistoryId(historyEntryId) .setDomainName("example.foo") - .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(Instant.parse("2010-11-12T05:00:00Z")) .setTokenType(SINGLE_USE) .build()); assertThat(loadByEntity(singleUseToken)).isEqualTo(singleUseToken); @@ -105,17 +104,17 @@ public class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder() .setToken("abc123Unlimited") .setTokenType(UNLIMITED_USE) - .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(Instant.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) + .setTokenStatusTransitionsInstant( + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), TokenStatus.VALID) + .put(fakeClock.now().plus(java.time.Duration.ofDays(56)), TokenStatus.ENDED) .build()) .setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.RENEW)) .build()); @@ -130,7 +129,7 @@ public class AllocationTokenTest extends EntityTestCase { .setToken("abc123Single") .setRedemptionHistoryId(historyEntryId) .setDomainName("example.foo") - .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) + .setCreationTimeForTest(Instant.parse("2010-11-12T05:00:00Z")) .setTokenType(SINGLE_USE) .build()); persisted = loadByEntity(singleUseToken); @@ -143,7 +142,7 @@ public class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder().setToken("abc123").setTokenType(SINGLE_USE).build(); assertThat(tokenBeforePersisting.getCreationTime()).isEmpty(); AllocationToken tokenAfterPersisting = persistResource(tokenBeforePersisting); - assertThat(tokenAfterPersisting.getCreationTime()).hasValue(fakeClock.nowUtc()); + assertThat(tokenAfterPersisting.getCreationTime()).hasValue(fakeClock.now()); } @Test @@ -199,11 +198,11 @@ public class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder() .setToken("foobar") .setTokenType(SINGLE_USE) - .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")); + .setCreationTimeForTest(Instant.parse("2010-11-12T05:00:00Z")); IllegalStateException thrown = assertThrows( IllegalStateException.class, - () -> builder.setCreationTimeForTest(DateTime.parse("2010-11-13T05:00:00Z"))); + () -> builder.setCreationTimeForTest(Instant.parse("2010-11-13T05:00:00Z"))); assertThat(thrown).hasMessageThat().isEqualTo("Creation time can only be set once"); } @@ -420,11 +419,15 @@ public class AllocationTokenTest extends EntityTestCase { IllegalArgumentException.class, () -> new AllocationToken.Builder() - .setTokenStatusTransitions( - ImmutableSortedMap.naturalOrder() - .put(DateTime.now(UTC), NOT_STARTED) - .put(DateTime.now(UTC).plusDays(1), TokenStatus.VALID) - .put(DateTime.now(UTC).plusDays(2), TokenStatus.ENDED) + .setTokenStatusTransitionsInstant( + ImmutableSortedMap.naturalOrder() + .put(fakeClock.now(), NOT_STARTED) + .put( + fakeClock.now().plus(java.time.Duration.ofDays(1)), + TokenStatus.VALID) + .put( + fakeClock.now().plus(java.time.Duration.ofDays(2)), + TokenStatus.ENDED) .build())); assertThat(thrown) .hasMessageThat() @@ -438,10 +441,10 @@ public class AllocationTokenTest extends EntityTestCase { IllegalArgumentException.class, () -> new AllocationToken.Builder() - .setTokenStatusTransitions( - ImmutableSortedMap.naturalOrder() - .put(START_OF_TIME, TokenStatus.VALID) - .put(DateTime.now(UTC), TokenStatus.ENDED) + .setTokenStatusTransitionsInstant( + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, TokenStatus.VALID) + .put(fakeClock.now(), TokenStatus.ENDED) .build())); assertThat(thrown) .hasMessageThat() @@ -459,18 +462,18 @@ public class AllocationTokenTest extends EntityTestCase { void testSetTransitions_badTransitionsFromValid() { // VALID can only go to ENDED or CANCELLED assertBadTransition( - ImmutableSortedMap.naturalOrder() - .put(START_OF_TIME, NOT_STARTED) - .put(DateTime.now(UTC), VALID) - .put(DateTime.now(UTC).plusDays(1), VALID) + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), VALID) + .put(fakeClock.now().plus(java.time.Duration.ofDays(1)), VALID) .build(), VALID, VALID); assertBadTransition( - ImmutableSortedMap.naturalOrder() - .put(START_OF_TIME, NOT_STARTED) - .put(DateTime.now(UTC), VALID) - .put(DateTime.now(UTC).plusDays(1), NOT_STARTED) + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), VALID) + .put(fakeClock.now().plus(java.time.Duration.ofDays(1)), NOT_STARTED) .build(), VALID, NOT_STARTED); @@ -707,20 +710,20 @@ public class AllocationTokenTest extends EntityTestCase { private void assertBadInitialTransition(TokenStatus status) { assertBadTransition( - ImmutableSortedMap.naturalOrder() - .put(START_OF_TIME, NOT_STARTED) - .put(DateTime.now(UTC), status) + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), status) .build(), NOT_STARTED, status); } private void assertBadTransition( - ImmutableSortedMap map, TokenStatus from, TokenStatus to) { + ImmutableSortedMap map, TokenStatus from, TokenStatus to) { IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, - () -> new AllocationToken.Builder().setTokenStatusTransitions(map)); + () -> new AllocationToken.Builder().setTokenStatusTransitionsInstant(map)); assertThat(thrown) .hasMessageThat() .isEqualTo( @@ -734,12 +737,12 @@ public class AllocationTokenTest extends EntityTestCase { IllegalArgumentException.class, () -> new AllocationToken.Builder() - .setTokenStatusTransitions( - ImmutableSortedMap.naturalOrder() - .put(START_OF_TIME, NOT_STARTED) - .put(DateTime.now(UTC), VALID) - .put(DateTime.now(UTC).plusDays(1), status) - .put(DateTime.now(UTC).plusDays(2), CANCELLED) + .setTokenStatusTransitionsInstant( + ImmutableSortedMap.naturalOrder() + .put(START_INSTANT, NOT_STARTED) + .put(fakeClock.now(), VALID) + .put(fakeClock.now().plus(java.time.Duration.ofDays(1)), status) + .put(fakeClock.now().plus(java.time.Duration.ofDays(2)), CANCELLED) .build())); assertThat(thrown) .hasMessageThat() diff --git a/core/src/test/java/google/registry/model/host/HostTest.java b/core/src/test/java/google/registry/model/host/HostTest.java index 85aa42146..1ae781fea 100644 --- a/core/src/test/java/google/registry/model/host/HostTest.java +++ b/core/src/test/java/google/registry/model/host/HostTest.java @@ -22,6 +22,7 @@ import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistNewRegistrars; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.HostSubject.assertAboutHosts; +import static google.registry.util.DateTimeUtils.toInstant; import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableList; @@ -36,6 +37,7 @@ import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferStatus; import google.registry.testing.DatabaseHelper; import google.registry.util.SerializeUtils; +import java.time.Instant; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -190,8 +192,12 @@ class HostTest extends EntityTestCase { @Test void testComputeLastTransferTime_hostNeverSwitchedDomains_domainWasNeverTransferred() { - domain = domain.asBuilder().setLastTransferTime(null).build(); - host = host.asBuilder().setLastTransferTime(null).setLastSuperordinateChange(null).build(); + domain = domain.asBuilder().setLastTransferTime((Instant) null).build(); + host = + host.asBuilder() + .setLastTransferTime((Instant) null) + .setLastSuperordinateChange((Instant) null) + .build(); assertThat(host.computeLastTransferTime(domain)).isNull(); } @@ -204,10 +210,10 @@ class HostTest extends EntityTestCase { host = host.asBuilder() .setCreationTimeForTest(day1) - .setLastTransferTime(null) - .setLastSuperordinateChange(null) + .setLastTransferTime((Instant) null) + .setLastSuperordinateChange((Instant) null) .build(); - assertThat(host.computeLastTransferTime(domain)).isEqualTo(day2); + assertThat(host.computeLastTransferTime(domain)).isEqualTo(toInstant(day2)); } @Test @@ -237,9 +243,9 @@ class HostTest extends EntityTestCase { // Host was transferred on Day 1. // Host was made subordinate to domain on Day 2. // Domain was never transferred. - domain = domain.asBuilder().setLastTransferTime(null).build(); + domain = domain.asBuilder().setLastTransferTime((Instant) null).build(); host = host.asBuilder().setLastTransferTime(day1).setLastSuperordinateChange(day2).build(); - assertThat(host.computeLastTransferTime(domain)).isEqualTo(day1); + assertThat(host.computeLastTransferTime(domain)).isEqualTo(toInstant(day1)); } @Test @@ -249,7 +255,7 @@ class HostTest extends EntityTestCase { // Host was made subordinate to domain on Day 3. domain = domain.asBuilder().setLastTransferTime(day2).build(); host = host.asBuilder().setLastTransferTime(day1).setLastSuperordinateChange(day3).build(); - assertThat(host.computeLastTransferTime(domain)).isEqualTo(day1); + assertThat(host.computeLastTransferTime(domain)).isEqualTo(toInstant(day1)); } @Test @@ -259,6 +265,6 @@ class HostTest extends EntityTestCase { // Domain was transferred on Day 3. domain = domain.asBuilder().setLastTransferTime(day3).build(); host = host.asBuilder().setLastTransferTime(day1).setLastSuperordinateChange(day2).build(); - assertThat(host.computeLastTransferTime(domain)).isEqualTo(day3); + assertThat(host.computeLastTransferTime(domain)).isEqualTo(toInstant(day3)); } } 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 608ed7c86..dc2a9ccb0 100644 --- a/core/src/test/java/google/registry/model/poll/PollMessageTest.java +++ b/core/src/test/java/google/registry/model/poll/PollMessageTest.java @@ -19,6 +19,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.loadByKey; import static google.registry.testing.DatabaseHelper.persistResource; +import static google.registry.util.DateTimeUtils.plusYears; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -79,7 +80,7 @@ public class PollMessageTest extends EntityTestCase { .setEventTime(fakeClock.nowUtc()) .setMsg("Test poll message") .setHistoryEntry(historyEntry) - .setAutorenewEndTime(fakeClock.nowUtc().plusDays(365)) + .setAutorenewEndTime(plusYears(fakeClock.nowUtc(), 1)) .setTargetId("foobar.foo") .build(); } @@ -158,7 +159,7 @@ public class PollMessageTest extends EntityTestCase { .setEventTime(fakeClock.nowUtc()) .setMsg("Test poll message") .setHistoryEntry(historyEntry) - .setAutorenewEndTime(fakeClock.nowUtc().plusDays(365)) + .setAutorenewEndTime(plusYears(fakeClock.nowUtc(), 1)) .setTargetId("foobar.foo") .build()); assertThat(tm().transact(() -> tm().loadByEntity(pollMessage))).isEqualTo(pollMessage); @@ -173,7 +174,7 @@ public class PollMessageTest extends EntityTestCase { .setEventTime(fakeClock.nowUtc()) .setMsg("Test poll message") .setHistoryEntry(historyEntry) - .setAutorenewEndTime(fakeClock.nowUtc().plusDays(365)) + .setAutorenewEndTime(plusYears(fakeClock.nowUtc(), 1)) .setTargetId("foobar.foo") .build()); PollMessage persisted = tm().transact(() -> tm().loadByEntity(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 902ca256e..f04d12fe7 100644 --- a/core/src/test/java/google/registry/model/registrar/RegistrarTest.java +++ b/core/src/test/java/google/registry/model/registrar/RegistrarTest.java @@ -48,6 +48,7 @@ import google.registry.testing.DatabaseHelper; import google.registry.util.CidrAddressBlock; import google.registry.util.SerializeUtils; import java.math.BigDecimal; +import java.time.Instant; import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.junit.jupiter.api.BeforeEach; @@ -480,7 +481,10 @@ class RegistrarTest extends EntityTestCase { IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, - () -> new Registrar.Builder().setLastExpiringCertNotificationSentDate(null).build()); + () -> + new Registrar.Builder() + .setLastExpiringCertNotificationSentDate((Instant) null) + .build()); assertThat(thrown) .hasMessageThat() .isEqualTo("Registrar lastExpiringCertNotificationSentDate cannot be null"); @@ -513,7 +517,7 @@ class RegistrarTest extends EntityTestCase { IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, - () -> new Registrar.Builder().setLastPocVerificationDate(null).build()); + () -> new Registrar.Builder().setLastPocVerificationDate((Instant) null).build()); assertThat(thrown) .hasMessageThat() .isEqualTo("Registrar lastPocVerificationDate cannot be null"); @@ -526,7 +530,7 @@ class RegistrarTest extends EntityTestCase { IllegalArgumentException.class, () -> new Registrar.Builder() - .setLastExpiringFailoverCertNotificationSentDate(null) + .setLastExpiringFailoverCertNotificationSentDate((Instant) null) .build()); assertThat(thrown) .hasMessageThat() diff --git a/core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java b/core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java index 1f835318e..560df6ee4 100644 --- a/core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java +++ b/core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java @@ -48,17 +48,17 @@ public class ClaimsListDaoTest { @Test void save_insertsClaimsListSuccessfully() { ClaimsList claimsList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label1", "key1", "label2", "key2")); claimsList = ClaimsListDao.save(claimsList); ClaimsList insertedClaimsList = ClaimsListDao.get(); assertClaimsListEquals(claimsList, insertedClaimsList); - assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc()); + assertThat(insertedClaimsList.getCreationTime()).isEqualTo(fakeClock.now()); } @Test void save_insertsClaimsListSuccessfully_withRetries() { ClaimsList claimsList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label1", "key1", "label2", "key2")); AtomicBoolean isFirstAttempt = new AtomicBoolean(true); tm().transact( () -> { @@ -69,15 +69,15 @@ public class ClaimsListDaoTest { } }); ClaimsList insertedClaimsList = ClaimsListDao.get(); - assertThat(insertedClaimsList.getTmdbGenerationTime()) - .isEqualTo(claimsList.getTmdbGenerationTime()); + assertThat(insertedClaimsList.getTmdbGenerationTimeInstant()) + .isEqualTo(claimsList.getTmdbGenerationTimeInstant()); assertThat(insertedClaimsList.getLabelsToKeys()).isEqualTo(claimsList.getLabelsToKeys()); - assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc()); + assertThat(insertedClaimsList.getCreationTime()).isEqualTo(fakeClock.now()); } @Test void save_claimsListWithNoEntries() { - ClaimsList claimsList = ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of()); + ClaimsList claimsList = ClaimsList.create(fakeClock.now(), ImmutableMap.of()); claimsList = ClaimsListDao.save(claimsList); ClaimsList insertedClaimsList = ClaimsListDao.get(); assertClaimsListEquals(claimsList, insertedClaimsList); @@ -92,9 +92,9 @@ public class ClaimsListDaoTest { @Test void getCurrent_returnsLatestClaims() { ClaimsList oldClaimsList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label1", "key1", "label2", "key2")); ClaimsList newClaimsList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label3", "key3", "label4", "key4")); oldClaimsList = ClaimsListDao.save(oldClaimsList); newClaimsList = ClaimsListDao.save(newClaimsList); assertClaimsListEquals(newClaimsList, ClaimsListDao.get()); @@ -104,11 +104,11 @@ public class ClaimsListDaoTest { void testDaoCaching_savesAndUpdates() { assertThat(ClaimsListDao.CACHE.getIfPresent(ClaimsListDao.class)).isNull(); ClaimsList oldList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label1", "key1", "label2", "key2")); oldList = ClaimsListDao.save(oldList); assertThat(ClaimsListDao.CACHE.getIfPresent(ClaimsListDao.class)).isEqualTo(oldList); ClaimsList newList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label3", "key3", "label4", "key4")); newList = ClaimsListDao.save(newList); assertThat(ClaimsListDao.CACHE.getIfPresent(ClaimsListDao.class)).isEqualTo(newList); } @@ -116,7 +116,7 @@ public class ClaimsListDaoTest { @Test void testEntryCaching_savesAndUpdates() { ClaimsList claimsList = - ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); + ClaimsList.create(fakeClock.now(), ImmutableMap.of("label1", "key1", "label2", "key2")); // Bypass the DAO to avoid the cache tm().transact(() -> tm().insert(claimsList)); ClaimsList fromDatabase = ClaimsListDao.get(); @@ -140,7 +140,7 @@ public class ClaimsListDaoTest { private void assertClaimsListEquals(ClaimsList left, ClaimsList right) { assertThat(left.getRevisionId()).isEqualTo(right.getRevisionId()); - assertThat(left.getTmdbGenerationTime()).isEqualTo(right.getTmdbGenerationTime()); + assertThat(left.getTmdbGenerationTimeInstant()).isEqualTo(right.getTmdbGenerationTimeInstant()); assertThat(left.getLabelsToKeys()).isEqualTo(right.getLabelsToKeys()); } } diff --git a/core/src/test/java/google/registry/mosapi/module/MosApiModuleTest.java b/core/src/test/java/google/registry/mosapi/module/MosApiModuleTest.java index f413164d5..b0f78239f 100644 --- a/core/src/test/java/google/registry/mosapi/module/MosApiModuleTest.java +++ b/core/src/test/java/google/registry/mosapi/module/MosApiModuleTest.java @@ -15,6 +15,7 @@ package google.registry.mosapi.module; import static com.google.common.truth.Truth.assertThat; +import static google.registry.util.DateTimeUtils.plusYears; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -32,7 +33,6 @@ import java.security.PrivateKey; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -156,8 +156,8 @@ public class MosApiModuleTest { this.generatedPrivateKey = keyPair.getPrivate(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss'Z'").withZone(ZoneId.of("UTC")); - Instant now = Instant.now(); - Instant end = now.plus(Duration.ofDays(365)); + Instant now = Instant.parse("2021-01-01T00:00:00Z"); + Instant end = plusYears(now, 1); // Convert string to Bouncy Castle Time objects Time notBefore = new Time(new ASN1GeneralizedTime(formatter.format(now))); Time notAfter = new Time(new ASN1GeneralizedTime(formatter.format(end))); diff --git a/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java b/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java index 068e462bb..c75c17a80 100644 --- a/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java @@ -29,7 +29,7 @@ import google.registry.rdap.RdapDataStructures.PublicId; import google.registry.rdap.RdapDataStructures.RdapConformance; import google.registry.rdap.RdapDataStructures.RdapStatus; import google.registry.rdap.RdapDataStructures.Remark; -import org.joda.time.DateTime; +import java.time.Instant; import org.junit.jupiter.api.Test; /** Unit tests for {@link RdapDataStructures}. */ @@ -121,7 +121,7 @@ final class RdapDataStructuresTest { Event.builder() .setEventAction(EventAction.REGISTRATION) .setEventActor("Event Actor") - .setEventDate(DateTime.parse("2012-04-03T14:54Z")) + .setEventDate(Instant.parse("2012-04-03T14:54:00Z")) .addLink(Link.builder().setHref("myHref").build()) .build(); assertThat(event.toJson()) @@ -141,7 +141,7 @@ final class RdapDataStructuresTest { EventWithoutActor event = EventWithoutActor.builder() .setEventAction(EventAction.REGISTRATION) - .setEventDate(DateTime.parse("2012-04-03T14:54Z")) + .setEventDate(Instant.parse("2012-04-03T14:54:00Z")) .addLink(Link.builder().setHref("myHref").build()) .build(); assertThat(event.toJson()) diff --git a/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java b/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java index e1450c748..2c00783fc 100644 --- a/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java +++ b/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java @@ -48,6 +48,7 @@ import google.registry.rdap.RdapObjectClasses.BoilerplateType; import google.registry.rdap.RdapObjectClasses.ReplyPayloadBase; import google.registry.rdap.RdapObjectClasses.TopLevelReplyObject; import google.registry.testing.FakeClock; +import java.time.Instant; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -150,7 +151,7 @@ class RdapJsonFormatterTest { makeDomain("fish.みんな", null, null, registrar) .asBuilder() .setCreationTimeForTest(clock.nowUtc()) - .setLastEppUpdateTime(null) + .setLastEppUpdateTime((java.time.Instant) null) .build()); // history entries @@ -330,7 +331,7 @@ class RdapJsonFormatterTest { RdapJsonFormatter.getLastHistoryByType(domainFull), RdapJsonFormatter.HistoryTimeAndRegistrar::modificationTime)) .containsExactlyEntriesIn( - ImmutableMap.of(TRANSFER, DateTime.parse("1999-12-01T00:00:00.000Z"))); + ImmutableMap.of(TRANSFER, Instant.parse("1999-12-01T00:00:00.000Z"))); } @Test diff --git a/core/src/test/java/google/registry/rde/RegistrarToXjcConverterTest.java b/core/src/test/java/google/registry/rde/RegistrarToXjcConverterTest.java index f31085ef7..df51c4e7c 100644 --- a/core/src/test/java/google/registry/rde/RegistrarToXjcConverterTest.java +++ b/core/src/test/java/google/registry/rde/RegistrarToXjcConverterTest.java @@ -33,6 +33,7 @@ import google.registry.xjc.rderegistrar.XjcRdeRegistrarPostalInfoEnumType; import google.registry.xjc.rderegistrar.XjcRdeRegistrarPostalInfoType; import google.registry.xjc.rderegistrar.XjcRdeRegistrarStatusType; import java.io.ByteArrayOutputStream; +import java.time.Instant; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -86,7 +87,7 @@ public class RegistrarToXjcConverterTest { .setUrl("http://www.goblinmen.example") .build(); registrar = cloneAndSetAutoTimestamps(registrar); // Set the creation time in 2013. - registrar = registrar.asBuilder().setLastUpdateTime(null).build(); + registrar = registrar.asBuilder().setLastUpdateTime((Instant) null).build(); clock.setTo(DateTime.parse("2014-01-01T00:00:00Z")); registrar = cloneAndSetAutoTimestamps(registrar); // Set the update time in 2014. } diff --git a/core/src/test/java/google/registry/schema/registrar/RegistrarDaoTest.java b/core/src/test/java/google/registry/schema/registrar/RegistrarDaoTest.java index dc26ccfd3..49b0654e7 100644 --- a/core/src/test/java/google/registry/schema/registrar/RegistrarDaoTest.java +++ b/core/src/test/java/google/registry/schema/registrar/RegistrarDaoTest.java @@ -88,7 +88,7 @@ public class RegistrarDaoTest { assertThat(persisted.getRegistrarId()).isEqualTo("registrarId"); assertThat(persisted.getRegistrarName()).isEqualTo("registrarName"); - assertThat(persisted.getCreationTime()).isEqualTo(fakeClock.nowUtc()); + assertThat(persisted.getCreationTime()).isEqualTo(fakeClock.now()); assertThat(persisted.getLocalizedAddress()) .isEqualTo( new RegistrarAddress.Builder() diff --git a/core/src/test/java/google/registry/testing/DomainSubject.java b/core/src/test/java/google/registry/testing/DomainSubject.java index 507a548c9..94b13ff40 100644 --- a/core/src/test/java/google/registry/testing/DomainSubject.java +++ b/core/src/test/java/google/registry/testing/DomainSubject.java @@ -17,6 +17,7 @@ package google.registry.testing; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Fact.simpleFact; import static com.google.common.truth.Truth.assertAbout; +import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.collect.ImmutableSet; @@ -28,6 +29,7 @@ import google.registry.model.domain.secdns.DomainDsData; import google.registry.model.eppcommon.AuthInfo; import google.registry.testing.TruthChainer.And; import google.registry.tmch.LordnTaskUtils.LordnPhase; +import java.time.Instant; import java.util.Set; import javax.annotation.Nullable; import org.joda.time.DateTime; @@ -77,17 +79,30 @@ public final class DomainSubject extends AbstractEppResourceSubject hasRegistrationExpirationTime(DateTime expiration) { + return hasRegistrationExpirationTime(toInstant(expiration)); + } + + public And hasRegistrationExpirationTime(Instant expiration) { return hasValue( - expiration, actual.getRegistrationExpirationDateTime(), "getRegistrationExpirationTime()"); + expiration, actual.getRegistrationExpirationTime(), "getRegistrationExpirationTime()"); } public And hasLastTransferTime(DateTime lastTransferTime) { - return hasValue(lastTransferTime, actual.getLastTransferTime(), "getLastTransferTime()"); + return hasLastTransferTime(toInstant(lastTransferTime)); + } + + public And hasLastTransferTime(Instant lastTransferTime) { + return hasValue( + lastTransferTime, toInstant(actual.getLastTransferTime()), "getLastTransferTime()"); } public And hasLastTransferTimeNotEqualTo(DateTime lastTransferTime) { + return hasLastTransferTimeNotEqualTo(toInstant(lastTransferTime)); + } + + public And hasLastTransferTimeNotEqualTo(Instant lastTransferTime) { return doesNotHaveValue( - lastTransferTime, actual.getLastTransferTime(), "getLastTransferTime()"); + lastTransferTime, toInstant(actual.getLastTransferTime()), "getLastTransferTime()"); } public And hasDeletePollMessage() { @@ -109,12 +124,21 @@ public final class DomainSubject extends AbstractEppResourceSubject hasAutorenewEndTime(DateTime autorenewEndTime) { + return hasAutorenewEndTime(toInstant(autorenewEndTime)); + } + + public And hasAutorenewEndTime(Instant autorenewEndTime) { checkArgumentNotNull(autorenewEndTime, "Use hasNoAutorenewEndTime() instead"); - return hasValue(autorenewEndTime, actual.getAutorenewEndTime(), "getAutorenewEndTime()"); + return hasValue( + autorenewEndTime, + toInstant(actual.getAutorenewEndTime().orElse(null)), + "getAutorenewEndTime()"); } public And hasNoAutorenewEndTime() { - return hasNoValue(actual.getAutorenewEndTime(), "getAutorenewEndTime()"); + return hasNoValue( + actual.getAutorenewEndTime().map(google.registry.util.DateTimeUtils::toInstant), + "getAutorenewEndTime()"); } public static SimpleSubjectBuilder assertAboutDomains() { diff --git a/core/src/test/java/google/registry/tools/CreateRegistrarCommandTest.java b/core/src/test/java/google/registry/tools/CreateRegistrarCommandTest.java index e6be401b4..de8b3a7be 100644 --- a/core/src/test/java/google/registry/tools/CreateRegistrarCommandTest.java +++ b/core/src/test/java/google/registry/tools/CreateRegistrarCommandTest.java @@ -22,7 +22,8 @@ import static google.registry.testing.DatabaseHelper.createTlds; import static google.registry.testing.DatabaseHelper.newTld; import static google.registry.testing.DatabaseHelper.persistNewRegistrar; import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.util.DateTimeUtils.START_OF_TIME; +import static google.registry.util.DateTimeUtils.START_INSTANT; +import static google.registry.util.DateTimeUtils.toDateTime; import static org.joda.money.CurrencyUnit.JPY; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.eq; @@ -42,6 +43,7 @@ import google.registry.flows.certs.CertificateChecker.InsecureCertificateExcepti import google.registry.model.registrar.Registrar; import java.io.IOException; import java.math.BigDecimal; +import java.time.Instant; import java.util.Optional; import org.joda.money.CurrencyUnit; import org.joda.money.Money; @@ -62,7 +64,11 @@ class CreateRegistrarCommandTest extends CommandTestCase command.setConnection(connection); command.certificateChecker = new CertificateChecker( - ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398), + ImmutableSortedMap.of( + toDateTime(START_INSTANT), + 825, + toDateTime(Instant.parse("2020-09-01T00:00:00Z")), + 398), 30, 15, 2048, @@ -73,7 +79,7 @@ class CreateRegistrarCommandTest extends CommandTestCase @Test void testSuccess() throws Exception { - DateTime before = fakeClock.nowUtc(); + Instant before = fakeClock.now(); runCommandForced( "--name=blobio", "--password=some_password", @@ -87,7 +93,7 @@ class CreateRegistrarCommandTest extends CommandTestCase "--zip 00351", "--cc US", "clientz"); - DateTime after = fakeClock.nowUtc(); + Instant after = fakeClock.now(); Optional registrarOptional = Registrar.loadByRegistrarId("clientz"); assertThat(registrarOptional).isPresent(); @@ -101,7 +107,7 @@ class CreateRegistrarCommandTest extends CommandTestCase assertThat(registrar.getClientCertificateHash()).isEmpty(); assertThat(registrar.getPhonePasscode()).isEqualTo("01234"); assertThat(registrar.getCreationTime()).isIn(Range.closed(before, after)); - assertThat(registrar.getLastUpdateTime()).isEqualTo(registrar.getCreationTime()); + assertThat(registrar.getLastUpdateTimeInstant()).isEqualTo(registrar.getCreationTime()); assertThat(registrar.getBlockPremiumNames()).isFalse(); assertThat(registrar.isRegistryLockAllowed()).isFalse(); assertThat(registrar.getPoNumber()).isEmpty(); @@ -594,14 +600,14 @@ class CreateRegistrarCommandTest extends CommandTestCase newTld("foo", "FOO") .asBuilder() .setCurrency(JPY) - .setCreateBillingCostTransitions( - ImmutableSortedMap.of(START_OF_TIME, Money.of(JPY, new BigDecimal(1300)))) + .setCreateBillingCostTransitionsInstant( + ImmutableSortedMap.of(START_INSTANT, Money.of(JPY, new BigDecimal(1300)))) .setRestoreBillingCost(Money.of(JPY, new BigDecimal(1700))) .setServerStatusChangeBillingCost(Money.of(JPY, new BigDecimal(1900))) .setRegistryLockOrUnlockBillingCost(Money.of(JPY, new BigDecimal(2700))) - .setRenewBillingCostTransitions( - ImmutableSortedMap.of(START_OF_TIME, Money.of(JPY, new BigDecimal(1100)))) - .setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(JPY))) + .setRenewBillingCostTransitionsInstant( + ImmutableSortedMap.of(START_INSTANT, Money.of(JPY, new BigDecimal(1100)))) + .setEapFeeScheduleInstant(ImmutableSortedMap.of(START_INSTANT, Money.zero(JPY))) .setPremiumList(null) .build()); diff --git a/core/src/test/java/google/registry/tools/GetHistoryEntriesCommandTest.java b/core/src/test/java/google/registry/tools/GetHistoryEntriesCommandTest.java index 1c0a0d18a..8258cb40d 100644 --- a/core/src/test/java/google/registry/tools/GetHistoryEntriesCommandTest.java +++ b/core/src/test/java/google/registry/tools/GetHistoryEntriesCommandTest.java @@ -52,7 +52,7 @@ class GetHistoryEntriesCommandTest extends CommandTestCase @@ -104,7 +104,7 @@ class GetHistoryEntriesCommandTest extends CommandTestCase @@ -129,7 +129,7 @@ class GetHistoryEntriesCommandTest extends CommandTestCase diff --git a/core/src/test/java/google/registry/tools/MutatingCommandTest.java b/core/src/test/java/google/registry/tools/MutatingCommandTest.java index 76712d541..58719a780 100644 --- a/core/src/test/java/google/registry/tools/MutatingCommandTest.java +++ b/core/src/test/java/google/registry/tools/MutatingCommandTest.java @@ -100,18 +100,18 @@ public class MutatingCommandTest { assertThat(changes) .isEqualTo( """ - Update Host@2-ROID - lastEppUpdateTime: null -> 2014-09-09T09:09:09.000Z + Update Host@2-ROID + lastEppUpdateTime: null -> 2014-09-09T09:09:09.000Z - Update Host@3-ROID - currentSponsorRegistrarId: TheRegistrar -> Registrar2 + Update Host@3-ROID + currentSponsorRegistrarId: TheRegistrar -> Registrar2 - Update Registrar@Registrar1 - poNumber: null -> 23 + Update Registrar@Registrar1 + poNumber: null -> 23 - Update Registrar@Registrar2 - blockPremiumNames: false -> true - """); + Update Registrar@Registrar2 + blockPremiumNames: false -> true + """); String results = command.execute(); assertThat(results).isEqualTo("Updated 4 entities.\n"); assertThat(loadByEntity(host1)).isEqualTo(newHost1); @@ -217,12 +217,12 @@ public class MutatingCommandTest { assertThat(changes) .isEqualTo( """ - Update Host@2-ROID - [no changes] + Update Host@2-ROID + [no changes] - Update Registrar@Registrar1 - [no changes] - """); + Update Registrar@Registrar1 + [no changes] + """); String results = command.execute(); assertThat(results).isEqualTo("Updated 2 entities.\n"); assertThat(loadByEntity(host1)).isEqualTo(host1); diff --git a/core/src/test/java/google/registry/tools/UpdateRecurrenceCommandTest.java b/core/src/test/java/google/registry/tools/UpdateRecurrenceCommandTest.java index 4dad2807a..a4d5553f5 100644 --- a/core/src/test/java/google/registry/tools/UpdateRecurrenceCommandTest.java +++ b/core/src/test/java/google/registry/tools/UpdateRecurrenceCommandTest.java @@ -197,7 +197,7 @@ public class UpdateRecurrenceCommandTest extends CommandTestCase runCommandForced("domain.tld", "--renewal_price_behavior", "NONPREMIUM"))) .hasMessageThat() - .isEqualTo("Domain domain.tld's recurrence's end date is not END_OF_TIME"); + .isEqualTo("Domain domain.tld's recurrence's end date is not END_INSTANT"); } @Test diff --git a/core/src/test/java/google/registry/tools/server/GenerateZoneFilesActionTest.java b/core/src/test/java/google/registry/tools/server/GenerateZoneFilesActionTest.java index 9b92e86c1..e702db758 100644 --- a/core/src/test/java/google/registry/tools/server/GenerateZoneFilesActionTest.java +++ b/core/src/test/java/google/registry/tools/server/GenerateZoneFilesActionTest.java @@ -21,6 +21,7 @@ import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistActiveHost; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.TestDataHelper.loadFile; +import static google.registry.util.DateTimeUtils.toInstant; import static java.nio.charset.StandardCharsets.UTF_8; import static org.joda.time.Duration.standardDays; @@ -154,9 +155,11 @@ class GenerateZoneFilesActionTest { action.handleJsonRequest( ImmutableMap.of("tlds", ImmutableList.of("tld"), "exportTime", now)); assertThat(response) - .containsEntry("filenames", ImmutableList.of("gs://zonefiles-bucket/tld-" + now + ".zone")); + .containsEntry( + "filenames", ImmutableList.of("gs://zonefiles-bucket/tld-" + toInstant(now) + ".zone")); - BlobId gcsFilename = BlobId.of("zonefiles-bucket", String.format("tld-%s.zone", now)); + BlobId gcsFilename = + BlobId.of("zonefiles-bucket", String.format("tld-%s.zone", toInstant(now))); String generatedFile = new String(gcsUtils.readBytesFrom(gcsFilename), UTF_8); // The generated file contains spaces and tabs, but the golden file contains only spaces, as // files with literal tabs irritate our build tools. diff --git a/util/src/main/java/google/registry/util/InstantTypeAdapter.java b/util/src/main/java/google/registry/util/InstantTypeAdapter.java new file mode 100644 index 000000000..1d3cd2e33 --- /dev/null +++ b/util/src/main/java/google/registry/util/InstantTypeAdapter.java @@ -0,0 +1,47 @@ +// Copyright 2026 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.util; + +import static google.registry.util.DateTimeUtils.ISO_8601_FORMATTER; + +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.time.Instant; + +/** GSON type adapter for {@link Instant} objects. */ +public class InstantTypeAdapter extends StringBaseTypeAdapter { + + /** Parses an ISO-8601 string to an {@link Instant}. */ + @Override + protected Instant fromString(String stringValue) throws IOException { + try { + return DateTimeUtils.parseInstant(stringValue); + } catch (Exception e) { + throw new IOException(e); + } + } + + /** + * Writes the {@link Instant} to the given {@link JsonWriter} as a millisecond-precision string. + */ + @Override + public void write(JsonWriter writer, Instant value) throws IOException { + if (value == null) { + writer.value("null"); + } else { + writer.value(ISO_8601_FORMATTER.format(value)); + } + } +} diff --git a/util/src/test/java/google/registry/util/DateTimeUtilsTest.java b/util/src/test/java/google/registry/util/DateTimeUtilsTest.java index eaacd8308..a0a9dea65 100644 --- a/util/src/test/java/google/registry/util/DateTimeUtilsTest.java +++ b/util/src/test/java/google/registry/util/DateTimeUtilsTest.java @@ -23,8 +23,8 @@ import static google.registry.util.DateTimeUtils.earliestOf; import static google.registry.util.DateTimeUtils.isAtOrAfter; import static google.registry.util.DateTimeUtils.isBeforeOrAt; import static google.registry.util.DateTimeUtils.latestOf; -import static google.registry.util.DateTimeUtils.leapSafeAddYears; -import static google.registry.util.DateTimeUtils.leapSafeSubtractYears; +import static google.registry.util.DateTimeUtils.minusYears; +import static google.registry.util.DateTimeUtils.plusYears; import static google.registry.util.DateTimeUtils.toDateTime; import static google.registry.util.DateTimeUtils.toInstant; import static google.registry.util.DateTimeUtils.toLocalDate; @@ -50,10 +50,22 @@ class DateTimeUtilsTest { assertThat(DateTimeUtils.earliestDateTimeOf(sampleDates)).isEqualTo(START_OF_TIME); } + @Test + void testSuccess_earliestOf_instant() { + assertThat(earliestOf(START_INSTANT, END_INSTANT)).isEqualTo(START_INSTANT); + assertThat(earliestOf(ImmutableList.of(START_INSTANT, END_INSTANT))).isEqualTo(START_INSTANT); + } + @Test void testSuccess_latestOf() { assertThat(latestOf(START_OF_TIME, END_OF_TIME)).isEqualTo(END_OF_TIME); - assertThat(latestOf(sampleDates)).isEqualTo(END_OF_TIME); + assertThat(DateTimeUtils.latestDateTimeOf(sampleDates)).isEqualTo(END_OF_TIME); + } + + @Test + void testSuccess_latestOf_instant() { + assertThat(latestOf(START_INSTANT, END_INSTANT)).isEqualTo(END_INSTANT); + assertThat(latestOf(ImmutableList.of(START_INSTANT, END_INSTANT))).isEqualTo(END_INSTANT); } @Test @@ -71,47 +83,47 @@ class DateTimeUtilsTest { } @Test - void testSuccess_leapSafeAddYears() { + void testSuccess_plusYears() { DateTime startDate = DateTime.parse("2012-02-29T00:00:00Z"); assertThat(startDate.plusYears(4)).isEqualTo(DateTime.parse("2016-02-29T00:00:00Z")); - assertThat(leapSafeAddYears(startDate, 4)).isEqualTo(DateTime.parse("2016-02-28T00:00:00Z")); + assertThat(plusYears(startDate, 4)).isEqualTo(DateTime.parse("2016-02-28T00:00:00Z")); } @Test - void test_leapSafeAddYears_worksWithInstants() { + void test_plusYears_worksWithInstants() { Instant startDate = Instant.parse("2012-02-29T00:00:00Z"); - assertThat(leapSafeAddYears(startDate, 4)).isEqualTo(Instant.parse("2016-02-28T00:00:00Z")); + assertThat(plusYears(startDate, 4)).isEqualTo(Instant.parse("2016-02-28T00:00:00Z")); } @Test - void testSuccess_leapSafeSubtractYears() { + void testSuccess_minusYears() { DateTime startDate = DateTime.parse("2012-02-29T00:00:00Z"); assertThat(startDate.minusYears(4)).isEqualTo(DateTime.parse("2008-02-29T00:00:00Z")); - assertThat(leapSafeSubtractYears(startDate, 4)) - .isEqualTo(DateTime.parse("2008-02-28T00:00:00Z")); + assertThat(minusYears(startDate, 4)).isEqualTo(DateTime.parse("2008-02-28T00:00:00Z")); } @Test - void test_leapSafeSubtractYears_worksWithInstants() { + void test_minusYears_worksWithInstants() { Instant startDate = Instant.parse("2012-02-29T00:00:00Z"); - assertThat(leapSafeSubtractYears(startDate, 4)) - .isEqualTo(Instant.parse("2008-02-28T00:00:00Z")); + assertThat(minusYears(startDate, 4)).isEqualTo(Instant.parse("2008-02-28T00:00:00Z")); } @Test - void testSuccess_leapSafeSubtractYears_zeroYears() { + void testSuccess_minusYears_zeroYears() { DateTime leapDay = DateTime.parse("2012-02-29T00:00:00Z"); assertThat(leapDay.minusYears(0)).isEqualTo(leapDay); } @Test void testFailure_earliestOfEmpty() { - assertThrows(IllegalArgumentException.class, () -> earliestOf(ImmutableList.of())); + assertThrows( + IllegalArgumentException.class, () -> DateTimeUtils.earliestDateTimeOf(ImmutableList.of())); } @Test void testFailure_latestOfEmpty() { - assertThrows(IllegalArgumentException.class, () -> latestOf(ImmutableList.of())); + assertThrows( + IllegalArgumentException.class, () -> DateTimeUtils.latestDateTimeOf(ImmutableList.of())); } @Test @@ -144,13 +156,15 @@ class DateTimeUtilsTest { @Test void test_instantConversionMethods_workCorrectly() { - assertThat(toInstant(DateTime.parse("2024-03-27T10:15:30.105Z"))) - .isEqualTo(Instant.parse("2024-03-27T10:15:30.105Z")); assertThat(toDateTime(Instant.parse("2024-03-27T10:15:30.105Z"))) .isEqualTo(DateTime.parse("2024-03-27T10:15:30.105Z")); - assertThat(toInstant(toDateTime(Instant.parse("2024-03-27T10:15:30.105Z")))) + assertThat(toInstant(DateTime.parse("2024-03-27T10:15:30.105Z"))) .isEqualTo(Instant.parse("2024-03-27T10:15:30.105Z")); - assertThat(toDateTime(toInstant(DateTime.parse("2024-03-27T10:15:30.105Z")))) - .isEqualTo(DateTime.parse("2024-03-27T10:15:30.105Z")); + assertThat(DateTimeUtils.toJodaInstant(Instant.parse("2024-03-27T10:15:30.105Z"))) + .isEqualTo(org.joda.time.Instant.parse("2024-03-27T10:15:30.105Z")); + assertThat(DateTimeUtils.parseInstant("2024-03-27T10:15:30.105Z")) + .isEqualTo(Instant.parse("2024-03-27T10:15:30.105Z")); + assertThat(DateTimeUtils.parseInstant("2024-03-27T10:15:30Z")) + .isEqualTo(Instant.parse("2024-03-27T10:15:30Z")); } }