1
0
mirror of https://github.com/google/nomulus synced 2026-07-09 09:37:04 +00:00

Migrate core temporal models and related entities to java.time.Instant (#3001)

This comprehensive refactor continues the migration from Joda-Time to java.time (Instant), focusing on core timestamp models, transition properties, and their integration across the codebase.

Key changes:
- Migrated CreateAutoTimestamp and UpdateAutoTimestamp to use Instant internally, providing Joda-Time bridge methods for backward compatibility.
- Updated TimedTransitionProperty to handle Instant-based transition maps and updated corresponding Hibernate UserTypes (TimedTransitionBaseUserType).
- Migrated GracePeriod, BillingBase, BillingEvent, PollMessage, and PendingActionNotificationResponse fields (e.g., expirationTime, eventTime) to Instant.
- Migrated additional core entities (DomainBase, Registrar, HostBase, LaunchNotice, BsaLabel, DomainTransactionRecord) to use Instant for registrationExpirationTime, lastTransferTime, creationTime, etc.
- Updated Tld and FeatureFlag models to use Instant for claimsPeriodEnd, bsaEnrollStartTime, and status transitions.
- Enhanced CLI tools and parameters (TransitionListParameter, InstantParameter, RequestParameters) to support Instant-based input and output.
- Updated EntityYamlUtils with custom Instant serializers/deserializers to maintain format consistency (e.g., .SSSZ precision) required for YAML-based tests.
- Implemented UtcInstantAdapter to ensure JAXB XML serialization maintains millisecond accuracy, matching legacy Joda-Time behavior.
- Resolved Hibernate 6 type mismatches in JPQL and Native queries by ensuring consistent use of Instant for comparisons.
- Updated GEMINI.md with project-specific engineering standards, including the 'one commit per PR' mandate, full-build validation requirement, and commit message style rules.
- Cleaned up unnecessary @JsonIgnore and @JsonProperty annotations that were previously added to methods with parameters or redundant fields.
- Refactored DateTimeUtils to use strongly-typed overloads and standardized naming (earliestOf, latestOf) while avoiding type erasure clashes.
- Cleaned up fully qualified calls to toDateTime and toInstant by adding static imports across core model and flow files.
- Refactored test suites to use clock.now() (Instant) instead of nowUtc() (DateTime) and removed custom Truth subjects in favor of standard assertions.
This commit is contained in:
Ben McIlwain
2026-04-20 10:03:20 -04:00
committed by GitHub
parent db733aa50f
commit c427f2cc9f
169 changed files with 3512 additions and 1211 deletions
+75 -2
View File
@@ -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<DateTime>` and `Optional<Instant>` will clash due to erasure. Use distinct names, e.g., `setAutorenewEndTimeInstant(Optional<Instant> 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 <number> --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.
@@ -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.
*
* <p>Example: {@code 2024-03-27T10:15:30.105Z}
*
* <p>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}.
*
* <p>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<DateTime> dates) {
public static DateTime latestDateTimeOf(Iterable<DateTime> dates) {
checkArgument(!Iterables.isEmpty(dates));
return Ordering.<DateTime>natural().max(dates);
}
/** Returns the latest element in a {@link Instant} iterable. */
public static Instant latestOf(Iterable<Instant> instants) {
checkArgument(!Iterables.isEmpty(instants));
return Ordering.<Instant>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();
}
@@ -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);
}
}
@@ -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));
+1 -1
View File
@@ -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(
@@ -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<DateTime> provideStartTime(HttpServletRequest req) {
return extractOptionalDatetimeParameter(req, ExpandBillingRecurrencesAction.PARAM_START_TIME);
static Optional<Instant> provideStartTime(HttpServletRequest req) {
return extractOptionalInstantParameter(req, ExpandBillingRecurrencesAction.PARAM_START_TIME);
}
@Provides
@Parameter(ExpandBillingRecurrencesAction.PARAM_END_TIME)
static Optional<DateTime> provideEndTime(HttpServletRequest req) {
return extractOptionalDatetimeParameter(req, ExpandBillingRecurrencesAction.PARAM_END_TIME);
static Optional<Instant> provideEndTime(HttpServletRequest req) {
return extractOptionalInstantParameter(req, ExpandBillingRecurrencesAction.PARAM_END_TIME);
}
@Provides
@@ -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();
@@ -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.");
@@ -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<Domain> domainList =
query.setMaxResults(batchSize).getResultStream().collect(toImmutableList());
ImmutableList.Builder<String> domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
@@ -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<DateTime> startTimeParam;
Optional<Instant> startTimeParam;
@Inject
@Parameter(PARAM_END_TIME)
Optional<DateTime> endTimeParam;
Optional<Instant> 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<String, String>()
.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());
@@ -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<DateTime> eventTimes;
ImmutableSet<Instant> 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<DateTime> existingEventTimes =
ImmutableSet<Instant> existingEventTimes =
ImmutableSet.copyOf(
tm().query(
"SELECT eventTime FROM BillingEvent WHERE cancellationMatchingBillingEvent ="
+ " :key",
DateTime.class)
Instant.class)
.setParameter("key", billingRecurrence.createVKey())
.getResultList());
Set<DateTime> eventTimesToExpand = difference(eventTimes, existingEventTimes);
Set<Instant> 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(
@@ -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())));
@@ -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<String, String> 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());
@@ -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()) {
@@ -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<String> verifyDomainStillUnblockableWithReason(
UnblockableDomain domain, ImmutableMap<String, Domain> 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<String> checkMissingUnblockableDomains() {
DateTime now = clock.nowUtc();
Instant now = clock.now();
ImmutableList.Builder<String> errors = new ImmutableList.Builder<>();
errors.addAll(checkForMissingReservedUnblockables(now));
errors.addAll(checkForMissingRegisteredUnblockables(now));
return errors.build();
}
ImmutableList<String> checkForMissingRegisteredUnblockables(DateTime now) {
ImmutableList<String> checkForMissingRegisteredUnblockables(Instant now) {
ImmutableList.Builder<String> errors = new ImmutableList.Builder<>();
ImmutableList<Tld> 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<String> checkForMissingReservedUnblockables(DateTime now) {
ImmutableList<String> checkForMissingReservedUnblockables(Instant now) {
ImmutableList.Builder<String> errors = new ImmutableList.Builder<>();
try (Stream<ImmutableList<String>> reservedNames =
toBatches(
@@ -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<String> getAllReservedNames(DateTime now) {
return getAllReservedNames(toInstant(now));
}
public static Stream<String> 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<String> getAllReservedDomainsInTld(Tld tld, DateTime now) {
static ImmutableSet<String> 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<String> 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<InternetDomainName> 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));
}
}
@@ -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<String> 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<String> unavailableDomains, DateTime runTime) {
boolean uploadToGcs(ImmutableSortedSet<String> 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<String> unavailableDomains, DateTime runTime) {
boolean uploadToBsa(ImmutableSortedSet<String> 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<String> getUnavailableDomains(DateTime runTime) {
private ImmutableSortedSet<String> getUnavailableDomains(Instant runTime) {
// Get list of TLDs to process.
ImmutableSet<Tld> bsaEnabledTlds =
getTldEntitiesOfType(TldType.REAL).stream()
@@ -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();
}
@@ -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() {
@@ -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;
}
@@ -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() {}
@@ -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<String> getNewlyCreatedUnblockables(
DateTime prevRefreshStartTime, DateTime now) {
Instant prevRefreshStartTime, Instant now) {
ImmutableSet<String> bsaEnabledTlds =
getTldEntitiesOfType(TldType.REAL).stream()
.filter(tld -> isEnrolledWithBsa(tld, now))
@@ -260,7 +259,7 @@ public final class DomainsRefresher {
return getBlockedDomainNames(liveDomains);
}
static ImmutableSet<String> getAllReservedUnblockables(DateTime now, int batchSize) {
static ImmutableSet<String> getAllReservedUnblockables(Instant now, int batchSize) {
Stream<String> allReserved = getAllReservedNames(now);
return BatchedStreams.toBatches(allReserved, batchSize)
.map(DomainsRefresher::getBlockedDomainNames)
@@ -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<CompletedJob> latestCompleted,
@@ -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());
}
/**
@@ -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<String> queryNewlyCreatedDomains(
ImmutableCollection<String> tlds, DateTime minCreationTime, DateTime now) {
ImmutableCollection<String> 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<DomainLifeSpan> 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) {}
}
@@ -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(),
@@ -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);
}
@@ -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();
}
});
@@ -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();
}
@@ -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<SecDnsCreateExtension> 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))
@@ -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
@@ -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
@@ -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();
}
@@ -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<FeeRenewCommandExtension> feeRenew =
eppInput.getSingleExtension(FeeRenewCommandExtension.class);
@@ -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)
@@ -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();
@@ -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)
@@ -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);
}
@@ -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
@@ -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<PollMessage> 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<PollMessage> 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<PollMessage> 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<PollMessage> createPollMessageQuery(String registrarId, Instant now) {
return tm().createQueryComposer(PollMessage.class)
.where("clientId", EQ, registrarId)
.where("eventTime", LTE, now);
@@ -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<PollMessage> maybePollMessage = getFirstPollMessage(registrarId, now);
if (maybePollMessage.isEmpty()) {
return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build();
@@ -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));
}
}
@@ -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<E extends Enum<E>>
extends StdSerializer<TimedTransitionProperty<E>> {
TimedTransitionPropertySerializer() {
super(null, true);
}
@Override
public void serialize(
TimedTransitionProperty<E> 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<TimedTransitionProperty<TldState>> {
@@ -346,11 +333,13 @@ public class EntityYamlUtils {
public TimedTransitionProperty<TldState> deserialize(
JsonParser jp, DeserializationContext context) throws IOException {
SortedMap<String, String> 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<Money> deserialize(JsonParser jp, DeserializationContext context)
throws IOException {
SortedMap<String, LinkedHashMap<String, Object>> 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<FeatureStatus> deserialize(
JsonParser jp, DeserializationContext context) throws IOException {
SortedMap<String, String> 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<CreateAutoTimestamp> {
public CreateAutoTimestampSerializer() {
this(null);
}
public CreateAutoTimestampSerializer(Class<CreateAutoTimestamp> 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<CreateAutoTimestamp> {
@@ -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<Instant> {
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<Instant> {
public InstantDeserializer() {
super(Instant.class);
}
@Override
public Instant deserialize(JsonParser jp, DeserializationContext context) throws IOException {
return parseInstant(jp.getText());
}
}
}
@@ -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 <update>}. 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
* <p>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 <update>} 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 <update>} 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());
}
}
@@ -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.
*
* <p>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 extends EppResource> 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 extends EppResource> 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 extends EppResource> 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<VKey<Domain>> getLinkedDomainKeys(
VKey<Host> 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<VKey<Domain>> getLinkedDomainKeys(
VKey<Host> 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<Host> 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<Host> key, Instant now) {
return !getLinkedDomainKeys(key, now, 1).isEmpty();
}
@@ -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.
*
* <p>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 <E extends EppResource> Optional<VKey<E>> loadKey(
Class<E> 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 <E extends EppResource> Optional<VKey<E>> loadKey(
Class<E> clazz, String foreignKey, DateTime now) {
Class<E> clazz, String foreignKey, Instant now) {
return Optional.ofNullable(loadKeys(clazz, ImmutableList.of(foreignKey), now).get(foreignKey));
}
@@ -81,13 +106,14 @@ public final class ForeignKeyUtils {
*
* <p>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 <E extends EppResource> Optional<E> loadResource(
Class<E> 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 {
*
* <p>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 <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeys(
Class<E> clazz, Collection<String> 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.
*
* <p>The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist
* or has been soft-deleted.
*/
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeys(
Class<E> clazz, Collection<String> 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 {
* <p>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 <E extends EppResource> ImmutableMap<String, E> loadResources(
Class<E> clazz, Collection<String> 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 {
*
* <p>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 <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeysByCacheIfEnabled(
Class<E> clazz, Collection<String> 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.
*
* <p>The returned map will omit any keys for which the {@link EppResource} doesn't exist or has
* been soft-deleted.
*
* <p>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 <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeysByCacheIfEnabled(
Class<E> clazz, Collection<String> 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 <E extends EppResource> Optional<VKey<E>> loadKeyByCache(
Class<E> 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 <E extends EppResource> Optional<VKey<E>> loadKeyByCache(
Class<E> 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 {
*
* <p>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 <E extends EppResource> Optional<E> loadResourceByCacheIfEnabled(
Class<E> 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.
*
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
* created resource was deleted before time "now".
*
* <p>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.
*
* <p>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 <E extends EppResource> Optional<E> loadResourceByCacheIfEnabled(
Class<E> 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.
*
* <p>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 <E extends EppResource> Optional<E> loadResourceByCache(
Class<E> 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 <E extends EppResource> Optional<E> loadResourceByCache(
Class<E> clazz, String foreignKey, DateTime now) {
Class<E> clazz, String foreignKey, Instant now) {
return (Optional<E>)
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));
}
}
@@ -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<String, Object> sortedFields = new TreeMap<>();
for (Entry<Field, Object> 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<String, Object> sortedFields = new TreeMap<>();
for (Entry<Field, Object> 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 {
@@ -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);
}
}
@@ -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));
}
}
@@ -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);
}
/**
@@ -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();
}
@@ -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 "
@@ -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.
*
* <p>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();
}
}
@@ -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<Cursor> 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> cursor) {
return cursor.map(Cursor::getCursorTime).orElse(START_OF_TIME);
public static Instant getCursorTimeOrStartOfTimeInstant(Optional<Cursor> cursor) {
return cursor.map(Cursor::getCursorTimeInstant).orElse(START_INSTANT);
}
/**
* @deprecated Use {@link #getCursorTimeOrStartOfTimeInstant(Optional)}
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public static DateTime getCursorTimeOrStartOfTime(Optional<Cursor> cursor) {
return toDateTime(getCursorTimeOrStartOfTimeInstant(cursor));
}
/**
* @deprecated Use {@link #getCursorTimeInstant()}
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public DateTime getCursorTime() {
return toDateTime(cursorTime);
}
public Instant getCursorTimeInstant() {
return cursorTime;
}
@@ -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<DateTime, FeatureStatus> 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<Instant, FeatureStatus> statusMap) {
getInstance().status = TimedTransitionProperty.fromValueMapInstant(statusMap);
return this;
}
}
@@ -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 {
*
* <p>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}.
*
* <p>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).
*
* <p>WARNING: This can return a potentially very large {@link Iterable} if {@code END_OF_TIME}
* is used as the upper endpoint of the range.
* <p>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<DateTime> getInstancesInRange(Range<DateTime> 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).
*
* <p>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<Instant> getInstancesInRangeInstant(Range<Instant> 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<Instant> normalizedRange = range.intersection(Range.closed(START_INSTANT, END_INSTANT));
Range<Integer> 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<String> 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);
}
/**
@@ -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<V extends Serializable> 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 <V extends Serializable> TimedTransitionProperty<V> fromValueMap(
ImmutableSortedMap<DateTime, V> 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<Instant, V> backingMap;
/**
* Returns a new immutable {@link TimedTransitionProperty} with an initial value at {@code
* START_OF_TIME}.
*/
public static <V extends Serializable> TimedTransitionProperty<V> 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
* <p>This is used for JSON/YAML serialization.
*/
public static <V extends Serializable> TimedTransitionProperty<V> make(
ImmutableSortedMap<DateTime, V> newTransitions,
ImmutableMultimap<V, V> allowedTransitions,
String allowedTransitionMapName,
V initialValue,
String badInitialValueErrorMessage) {
validateTimedTransitionMap(newTransitions, allowedTransitions, allowedTransitionMapName);
checkArgument(
newTransitions.firstEntry().getValue() == initialValue, badInitialValueErrorMessage);
return fromValueMap(newTransitions);
@JsonValue
public ImmutableSortedMap<String, V> 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 <V extends Serializable> void validateTimedTransitionMap(
@Nullable NavigableMap<DateTime, V> transitionMap,
ImmutableMultimap<V, V> allowedTransitions,
String mapName) {
private TimedTransitionProperty(ImmutableSortedMap<Instant, V> 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<V> 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<DateTime, V> backingMap;
/** Returns a new {@link TimedTransitionProperty} backed by the provided map instance. */
private TimedTransitionProperty(NavigableMap<DateTime, V> 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 <V extends Serializable> TimedTransitionProperty<V> 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 <V extends Serializable> TimedTransitionProperty<V> 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.
*
* <p>The map must contain a value for {@code START_INSTANT}.
*
* @deprecated Use {@link #fromValueMapInstant(ImmutableSortedMap)}
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public static <V extends Serializable> TimedTransitionProperty<V> fromValueMap(
ImmutableSortedMap<DateTime, V> valueMap) {
return fromValueMapInstant(toInstantMap(valueMap));
}
/**
* Returns a {@link TimedTransitionProperty} that contains the transition values and times defined
* in the given map.
*
* <p>The map must contain a value for {@code START_INSTANT}.
*/
public static <V extends Serializable> TimedTransitionProperty<V> fromValueMapInstant(
ImmutableSortedMap<Instant, V> valueMap) {
return new TimedTransitionProperty<>(valueMap);
}
/**
* Returns a {@link TimedTransitionProperty} that contains the transition values and times defined
* in the given map.
*
* <p>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 <V extends Serializable> TimedTransitionProperty<V> make(
ImmutableSortedMap<DateTime, V> valueMap,
ImmutableMultimap<V, V> 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.
*
* <p>The map must contain a value for {@code START_INSTANT}. The map is also validated against a
* set of allowed transitions.
*/
public static <V extends Serializable> TimedTransitionProperty<V> makeInstant(
ImmutableSortedMap<Instant, V> valueMap,
ImmutableMultimap<V, V> 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 <V extends Serializable> void validateTimedTransitionMap(
ImmutableSortedMap<DateTime, V> valueMap,
ImmutableMultimap<V, V> allowedTransitions,
String mapName) {
validateTimedTransitionMapInstant(toInstantMap(valueMap), allowedTransitions, mapName);
}
/** Validates a timed transition map. */
public static <V extends Serializable> void validateTimedTransitionMapInstant(
ImmutableSortedMap<Instant, V> valueMap,
ImmutableMultimap<V, V> 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 <V> ImmutableSortedMap<Instant, V> toInstantMap(
ImmutableSortedMap<DateTime, V> 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<DateTime, V> 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<Instant, V> 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<V extends Serializable> 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(", ", "{", "}"));
}
}
@@ -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;
}
@@ -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<String> 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 {
*
* <p>Can be null if the resource has never been transferred.
*/
@Expose DateTime lastTransferTime;
@Expose Instant lastTransferTime;
/**
* When the domain's autorenewal status will expire.
*
* <p>This will be {@link DateTimeUtils#END_OF_TIME} for the vast majority of domains because all
* <p>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<OneTime> getDeletePollMessage() {
@@ -326,19 +330,37 @@ public class DomainBase extends EppResource {
/**
* Returns the autorenew end time if there is one, otherwise empty.
*
* <p>Note that {@link DateTimeUtils#END_OF_TIME} is used as a sentinel value in the database
* <p>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<DateTime> 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<Instant> 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<OneTime> 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<DateTime> autorenewEndTime) {
getInstance().autorenewEndTime = autorenewEndTime.orElse(END_OF_TIME);
return setAutorenewEndTimeInstant(autorenewEndTime.map(DateTimeUtils::toInstant));
}
public B setAutorenewEndTimeInstant(Optional<Instant> 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<AllocationToken> currentBulkToken) {
if (currentBulkToken == null) {
getInstance().currentBulkToken = currentBulkToken;
@@ -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);
}
}
@@ -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> billingEvent,
@Nullable VKey<BillingRecurrence> 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<BillingEvent> 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<BillingEvent> 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<BillingEvent> 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<BillingEvent> 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<BillingRecurrence> 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<BillingRecurrence> 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<BillingRecurrence> 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<BillingRecurrence> 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;
@@ -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() {
@@ -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<DateTime> 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 &gt;= 1 hour ago and has not been verified. */
/**
* Returns true iff the lock was requested &gt;= 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 &gt;= 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 &gt;= 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 &gt;= 1 hour ago and has not been verified. */
public boolean isUnlockRequestExpired(DateTime now) {
public boolean isUnlockRequestExpired(Instant now) {
Optional<DateTime> 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
@@ -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<DateTime> validDateRange;
@XmlTransient Range<Instant> 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<DateTime> getValidDateRange() {
checkState(hasValidDateRange());
return validDateRange;
if (validDateRange != null) {
return validDateRange;
}
return convertRange(validDateRangeInstant, google.registry.util.DateTimeUtils::toDateTime);
}
@JsonIgnore
public Range<Instant> getValidDateRangeInstant() {
checkState(hasValidDateRange());
if (validDateRangeInstant != null) {
return validDateRangeInstant;
}
return convertRange(validDateRange, google.registry.util.DateTimeUtils::toInstant);
}
private static <S extends Comparable<? super S>, T extends Comparable<? super T>>
Range<T> convertRange(Range<S> range, java.util.function.Function<S, T> 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() {
@@ -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<Instant> 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) {
@@ -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));
}
}
@@ -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;
@@ -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;
@@ -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<DateTime> getCreationTime() {
public Optional<Instant> 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<DateTime> getCreationDateTime() {
return Optional.ofNullable(toDateTime(creationTime.getTimestamp()));
}
@JsonIgnore
public TimedTransitionProperty<TokenStatus> getTokenStatusTransitions() {
return tokenStatusTransitions;
}
public ImmutableSortedMap<DateTime, TokenStatus> getTokenStatusTransitionsMap() {
return tokenStatusTransitions.toValueMap();
}
@JsonIgnore
public ImmutableSortedMap<Instant, TokenStatus> getTokenStatusTransitionsMapInstant() {
return tokenStatusTransitions.toValueMapInstant();
}
public ImmutableSet<CommandName> 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<DateTime, TokenStatus> transitions) {
return setTokenStatusTransitionsInstant(
transitions.entrySet().stream()
.collect(
ImmutableSortedMap.toImmutableSortedMap(
Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue)));
}
public Builder setTokenStatusTransitionsInstant(
ImmutableSortedMap<Instant, TokenStatus> transitions) {
getInstance().tokenStatusTransitions =
TimedTransitionProperty.make(
TimedTransitionProperty.makeInstant(
transitions,
VALID_TOKEN_STATUS_TRANSITIONS,
"tokenStatusTransitions",
@@ -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<DateTime> getLastNotificationSent() {
return Optional.ofNullable(toDateTime(lastNotificationSent));
}
public Optional<Instant> 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;
}
@@ -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;
@@ -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 {
*
* <p>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<InetAddress> 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())
@@ -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;
@@ -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 extends PendingActionNotificationResponse> 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));
}
}
}
@@ -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<ResponseData> getResponseData() {
return NullIgnoringCollectionBuilder.create(new ImmutableList.Builder<ResponseData>())
@@ -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<ResponseData> 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();
}
}
@@ -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;
@@ -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() {
@@ -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</a>
*/
@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;
@@ -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();
@@ -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<HistoryEntry> 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<HistoryEntry> loadAllHistoryObjects(
Instant afterTime, Instant beforeTime) {
return tm().transact(
() ->
new ImmutableList.Builder<HistoryEntry>()
@@ -63,7 +76,7 @@ public class HistoryEntryDao {
/** Loads all history objects corresponding to the given {@link EppResource}. */
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
VKey<? extends EppResource> 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 <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
VKey<? extends EppResource> resourceKey, Class<T> 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<HistoryEntry> loadHistoryObjectsForResource(
VKey<? extends EppResource> 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<HistoryEntry> loadHistoryObjectsForResource(
VKey<? extends EppResource> resourceKey, DateTime afterTime, DateTime beforeTime) {
VKey<? extends EppResource> 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 <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
VKey<? extends EppResource> resourceKey,
DateTime afterTime,
DateTime beforeTime,
Class<T> 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 <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
public static <T extends HistoryEntry> ImmutableList<T> loadHistoryObjectsForResource(
VKey<? extends EppResource> resourceKey,
DateTime afterTime,
DateTime beforeTime,
Instant afterTime,
Instant beforeTime,
Class<T> subclazz) {
Class<? extends HistoryEntry> expectedSubclazz =
getHistoryClassFromParent(resourceKey.getKind());
checkArgument(
@@ -132,20 +170,22 @@ public class HistoryEntryDao {
}
private static ImmutableList<HistoryEntry> loadHistoryObjectsForResourceInternal(
VKey<? extends EppResource> resourceKey, DateTime afterTime, DateTime beforeTime) {
VKey<? extends EppResource> resourceKey, Instant afterTime, Instant beforeTime) {
// The class we're searching from is based on which resource type (e.g. Domain) we have
Class<? extends HistoryEntry> historyClass = getHistoryClassFromParent(resourceKey.getKind());
CriteriaBuilder criteriaBuilder = tm().getEntityManager().getCriteriaBuilder();
CriteriaQuery<? extends HistoryEntry> 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<? extends HistoryEntry> getHistoryClassFromParent(
@@ -158,7 +198,7 @@ public class HistoryEntryDao {
}
private static <T extends HistoryEntry> List<T> loadAllHistoryObjects(
Class<T> historyClass, DateTime afterTime, DateTime beforeTime) {
Class<T> historyClass, Instant afterTime, Instant beforeTime) {
CriteriaBuilder criteriaBuilder = tm().getEntityManager().getCriteriaBuilder();
return tm().criteriaQuery(
CriteriaQueryBuilder.create(historyClass)
@@ -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<DateTime> 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<Instant> getBsaEnrollStartTimeInstant() {
return Optional.ofNullable(this.bsaEnrollStartTime);
}
@@ -607,19 +622,43 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
* <p>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}.
*
* <p>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<Instant, Money> 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<Instant, TldState> getTldStateTransitionsInstant() {
return tldStateTransitions.toValueMapInstant();
}
public ImmutableSortedMap<DateTime, Money> getRenewBillingCostTransitions() {
return renewBillingCostTransitions.toValueMap();
}
@JsonIgnore
public ImmutableSortedMap<Instant, Money> getRenewBillingCostTransitionsInstant() {
return renewBillingCostTransitions.toValueMapInstant();
}
/** Returns the EAP fee for the tld at the given time. */
public Fee getEapFeeFor(DateTime now) {
ImmutableSortedMap<DateTime, Money> valueMap = getEapFeeScheduleAsMap();
DateTime periodStart = valueMap.floorKey(now);
DateTime periodEnd = valueMap.ceilingKey(now);
// NOTE: assuming END_OF_TIME would never be reached...
Range<DateTime> validPeriod =
return getEapFeeFor(toInstant(now));
}
/** Returns the EAP fee for the tld at the given time. */
public Fee getEapFeeFor(Instant now) {
ImmutableSortedMap<Instant, Money> valueMap = getEapFeeScheduleAsMapInstant();
Instant periodStart = valueMap.floorKey(now);
Instant periodEnd = valueMap.ceilingKey(now);
// NOTE: assuming END_INSTANT would never be reached...
Range<Instant> 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<Instant, Money> 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<DateTime, TldState> 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<Instant, TldState> 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<DateTime, Money> createCostsMap) {
return setCreateBillingCostTransitionsInstant(
createCostsMap.entrySet().stream()
.collect(
ImmutableSortedMap.toImmutableSortedMap(
Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue)));
}
public Builder setCreateBillingCostTransitionsInstant(
ImmutableSortedMap<Instant, Money> 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<DateTime, Money> renewCostsMap) {
return setRenewBillingCostTransitionsInstant(
renewCostsMap.entrySet().stream()
.collect(
ImmutableSortedMap.toImmutableSortedMap(
Ordering.natural(), e -> toInstant(e.getKey()), Map.Entry::getValue)));
}
public Builder setRenewBillingCostTransitionsInstant(
ImmutableSortedMap<Instant, Money> 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<DateTime, Money> 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<Instant, Money> 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<DateTime> enrollTime) {
// TODO(b/309175133): forbid if enrolled with BSA
getInstance().bsaEnrollStartTime = enrollTime.map(DateTimeUtils::toInstant).orElse(null);
return this;
}
public Builder setBsaEnrollStartTimeInstant(Optional<Instant> 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<Money> 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");
@@ -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));
}
}
@@ -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 <a href="https://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.1">DNL List
* creation datetime</a>
* @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<String, String> labelsToKeys) {
Instant tmdbGenerationTime, ImmutableMap<String, String> 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<String, String> labelsToKeys) {
return create(toInstant(tmdbGenerationTime), labelsToKeys);
}
}
@@ -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();
@@ -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<BillingEvent> serverApproveBillingEvent) {
getInstance().serverApproveBillingEvent = serverApproveBillingEvent;
return this;
@@ -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));
}
}
}
@@ -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;
@@ -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<V extends Serializable>
@Override
Map<String, String> toStringMap(TimedTransitionProperty<V> 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<V> toEntity(Map<String, String> map) {
ImmutableSortedMap<DateTime, V> valueMap =
ImmutableSortedMap<Instant, V> 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);
}
}
@@ -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;
}
@@ -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();
}
@@ -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<Link> links();
abstract static class Builder<B extends Builder<?>> {
abstract B setEventAction(EventAction eventAction);
abstract B setEventDate(DateTime eventDate);
abstract B setEventDate(Instant eventDate);
abstract ImmutableList.Builder<Link> linksBuilder();
@SuppressWarnings("unchecked")
@@ -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
@@ -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<VKey<Host>> 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<String> 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<VKey<Host>> 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<String> desiredRegistrar = getDesiredRegistrar();
ImmutableSet<VKey<Host>> 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<String, String> parameters =
new ImmutableMap.Builder<String, String>()
.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<String, Object> parameters =
new ImmutableMap.Builder<String, Object>()
.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());
@@ -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<String, ImmutableMap<EventAction, HistoryTimeAndRegistrar>>
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<StatusValue> statuses = new ImmutableSet.Builder<>();
statuses.addAll(host.getStatusValues());
VKey<Domain> 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.
*
* <p>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 {
* <p>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;
}
@@ -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.
@@ -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<String, String> parameters =
new ImmutableMap.Builder<String, String>()
.put("address", InetAddresses.toAddrString(inetAddress));
ImmutableMap.Builder<String, Object> parameters =
new ImmutableMap.Builder<String, Object>()
.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
@@ -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;
}
@@ -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));
}
@@ -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 <crDate> element that contains the date and time of registrar-
// object creation.
bean.setCrDate(model.getCreationTime());
bean.setCrDate(toDateTime(model.getCreationTime()));
// o An OPTIONAL <upDate> element that contains the date and time of
// the most recent RDE registrar-object modification. This element
@@ -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 <a
* href="https://goo.gl/pk5Q2k">ISO 8601</a> 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 <a
* href="https://goo.gl/pk5Q2k">ISO 8601</a> 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<Instant> 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 <a
* href="https://goo.gl/pk5Q2k">ISO 8601</a> timestamp, e.g. {@code 1984-12-18TZ}, {@code

Some files were not shown because too many files have changed in this diff Show More