From 11f7bb8f6e83cd54a86e95c4f9ad7a209481716c Mon Sep 17 00:00:00 2001 From: gbrodman Date: Tue, 4 May 2021 19:09:27 -0400 Subject: [PATCH] Populate the domain in DomainHistory objects created in Domain flows (#1106) Unfortunately, much of the time there's a bit of a circular dependency in the object creation, e.g. the Domain object stores references to the billing events which store references to the history object which contains the Domain object. As a result, we allocate the history object's ID before creating it, so that it can be referenced in the other objects that store that reference, e.g. billing events. In addition, we add a utility copyFrom method in HistoryEntry.Builder to avoid unnecessary ID allocations. --- .../google/registry/flows/FlowModule.java | 2 +- .../java/google/registry/flows/FlowUtils.java | 9 + .../flows/domain/DomainCreateFlow.java | 77 +++---- .../flows/domain/DomainDeleteFlow.java | 76 ++++--- .../flows/domain/DomainRenewFlow.java | 52 +++-- .../domain/DomainRestoreRequestFlow.java | 40 ++-- .../domain/DomainTransferApproveFlow.java | 76 ++++--- .../domain/DomainTransferCancelFlow.java | 24 ++- .../domain/DomainTransferRejectFlow.java | 26 ++- .../domain/DomainTransferRequestFlow.java | 31 ++- .../flows/domain/DomainTransferUtils.java | 104 +++++---- .../flows/domain/DomainUpdateFlow.java | 16 +- .../registry/model/billing/BillingEvent.java | 33 +-- .../model/reporting/HistoryEntry.java | 6 +- .../model/reporting/HistoryEntryDao.java | 34 +-- .../model/transfer/DomainTransferData.java | 2 + .../flows/EppLifecycleDomainTest.java | 14 +- .../registry/flows/ResourceFlowTestCase.java | 28 +++ .../flows/domain/DomainCreateFlowTest.java | 1 + .../flows/domain/DomainDeleteFlowTest.java | 1 + .../flows/domain/DomainRenewFlowTest.java | 117 ++++++----- .../domain/DomainRestoreRequestFlowTest.java | 120 ++++++----- .../domain/DomainTransferCancelFlowTest.java | 1 + .../domain/DomainTransferQueryFlowTest.java | 42 ++-- .../domain/DomainTransferRejectFlowTest.java | 1 + .../domain/DomainTransferRequestFlowTest.java | 1 + .../flows/domain/DomainUpdateFlowTest.java | 198 +++++++++--------- .../model/billing/BillingEventTest.java | 56 ++--- .../registry/testing/DatabaseHelper.java | 30 +-- .../registry/tools/EppLifecycleToolsTest.java | 4 +- .../registry/flows/poll_response_unrenew.xml | 2 +- .../google/registry/model/schema.txt | 8 +- 32 files changed, 670 insertions(+), 562 deletions(-) diff --git a/core/src/main/java/google/registry/flows/FlowModule.java b/core/src/main/java/google/registry/flows/FlowModule.java index 8683ed8ae..94c458e70 100644 --- a/core/src/main/java/google/registry/flows/FlowModule.java +++ b/core/src/main/java/google/registry/flows/FlowModule.java @@ -243,7 +243,7 @@ public class FlowModule { @Provides static DomainHistory.Builder provideDomainHistoryBuilder( HistoryEntry.Builder historyEntryBuilder) { - return new DomainHistory.Builder().copyFrom(historyEntryBuilder.build()); + return new DomainHistory.Builder().copyFrom(historyEntryBuilder); } /** diff --git a/core/src/main/java/google/registry/flows/FlowUtils.java b/core/src/main/java/google/registry/flows/FlowUtils.java index 7a1cd8daf..0aa7a83fc 100644 --- a/core/src/main/java/google/registry/flows/FlowUtils.java +++ b/core/src/main/java/google/registry/flows/FlowUtils.java @@ -22,15 +22,19 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Throwables; import com.google.common.flogger.FluentLogger; +import com.googlecode.objectify.Key; import google.registry.flows.EppException.CommandUseErrorException; import google.registry.flows.EppException.ParameterValueRangeErrorException; import google.registry.flows.EppException.SyntaxErrorException; import google.registry.flows.EppException.UnimplementedProtocolVersionException; import google.registry.flows.custom.EntityChanges; +import google.registry.model.EppResource; import google.registry.model.eppcommon.EppXmlTransformer; import google.registry.model.eppinput.EppInput.WrongProtocolVersionException; import google.registry.model.eppoutput.EppOutput; import google.registry.model.host.InetAddressAdapter.IpVersionMismatchException; +import google.registry.model.ofy.ObjectifyService; +import google.registry.model.reporting.HistoryEntry; import google.registry.model.translators.CurrencyUnitAdapter.UnknownCurrencyException; import google.registry.xml.XmlException; import java.util.List; @@ -99,6 +103,11 @@ public final class FlowUtils { } } + public static Key createHistoryKey( + EppResource parent, Class clazz) { + return Key.create(Key.create(parent), clazz, ObjectifyService.allocateId()); + } + /** Registrar is not logged in. */ public static class NotLoggedInException extends CommandUseErrorException { public NotLoggedInException() { diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 1a155ec98..46031fb7a 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -97,7 +97,6 @@ import google.registry.model.eppinput.EppInput; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.CreateData.DomainCreateData; import google.registry.model.eppoutput.EppResponse; -import google.registry.model.host.HostResource; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.ForeignKeyIndex; import google.registry.model.ofy.ObjectifyService; @@ -107,11 +106,11 @@ import google.registry.model.poll.PollMessage.Autorenew; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.Registry.TldType; +import google.registry.model.registry.label.ReservationType; import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; -import google.registry.persistence.VKey; import google.registry.tmch.LordnTaskUtils; import java.util.Optional; import javax.inject.Inject; @@ -302,10 +301,14 @@ public class DomainCreateFlow implements TransactionalFlow { validateFeeChallenge(targetId, now, feeCreate, feesAndCredits); Optional secDnsCreate = validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); - String repoId = createDomainRepoId(ObjectifyService.allocateId(), registry.getTldStr()); DateTime registrationExpirationTime = leapSafeAddYears(now, years); - DomainHistory domainHistory = - buildHistoryEntry(repoId, registry, now, period, registry.getAddGracePeriodLength()); + String repoId = createDomainRepoId(ObjectifyService.allocateId(), registry.getTldStr()); + Key domainHistoryKey = + Key.create( + Key.create(DomainBase.class, repoId), + DomainHistory.class, + ObjectifyService.allocateId()); + historyBuilder.setId(domainHistoryKey.getId()); // Bill for the create. BillingEvent.OneTime createBillingEvent = createOneTimeBillingEvent( @@ -315,14 +318,14 @@ public class DomainCreateFlow implements TransactionalFlow { isReserved(domainName, isSunriseCreate), years, feesAndCredits, - domainHistory, + domainHistoryKey, allocationToken, now); // Create a new autorenew billing event and poll message starting at the expiration time. BillingEvent.Recurring autorenewBillingEvent = - createAutorenewBillingEvent(domainHistory, registrationExpirationTime); + createAutorenewBillingEvent(domainHistoryKey, registrationExpirationTime); PollMessage.Autorenew autorenewPollMessage = - createAutorenewPollMessage(domainHistory, registrationExpirationTime); + createAutorenewPollMessage(domainHistoryKey, registrationExpirationTime); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); entitiesToSave.add(createBillingEvent, autorenewBillingEvent, autorenewPollMessage); // Bill for EAP cost, if any. @@ -330,14 +333,12 @@ public class DomainCreateFlow implements TransactionalFlow { entitiesToSave.add(createEapBillingEvent(feesAndCredits, createBillingEvent)); } - ImmutableSet.Builder statuses = new ImmutableSet.Builder<>(); - if (getReservationTypes(domainName).contains(NAME_COLLISION)) { - statuses.add(SERVER_HOLD); - entitiesToSave.add( - createNameCollisionOneTimePollMessage(targetId, domainHistory, clientId, now)); - } - - DomainBase newDomain = + ImmutableSet reservationTypes = getReservationTypes(domainName); + ImmutableSet statuses = + reservationTypes.contains(NAME_COLLISION) + ? ImmutableSet.of(SERVER_HOLD) + : ImmutableSet.of(); + DomainBase domain = new DomainBase.Builder() .setCreationClientId(clientId) .setPersistedCurrentSponsorClientId(clientId) @@ -348,34 +349,38 @@ public class DomainCreateFlow implements TransactionalFlow { .setAutorenewPollMessage(autorenewPollMessage.createVKey()) .setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null) .setSmdId(signedMarkId) - .setDsData(secDnsCreate.isPresent() ? secDnsCreate.get().getDsData() : null) + .setDsData(secDnsCreate.map(SecDnsCreateExtension::getDsData).orElse(null)) .setRegistrant(command.getRegistrant()) .setAuthInfo(command.getAuthInfo()) .setDomainName(targetId) - .setNameservers( - (ImmutableSet>) - command.getNameservers().stream().collect(toImmutableSet())) - .setStatusValues(statuses.build()) + .setNameservers(command.getNameservers().stream().collect(toImmutableSet())) + .setStatusValues(statuses) .setContacts(command.getContacts()) .addGracePeriod( GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent)) .build(); + DomainHistory domainHistory = + buildDomainHistory(domain, registry, now, period, registry.getAddGracePeriodLength()); + if (reservationTypes.contains(NAME_COLLISION)) { + entitiesToSave.add( + createNameCollisionOneTimePollMessage(targetId, domainHistory, clientId, now)); + } entitiesToSave.add( - newDomain, - domainHistory.asBuilder().setDomainContent(newDomain).build(), - ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()), - EppResourceIndex.create(Key.create(newDomain))); + domain, + domainHistory, + ForeignKeyIndex.create(domain, domain.getDeletionTime()), + EppResourceIndex.create(Key.create(domain))); if (allocationToken.isPresent() && TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) { entitiesToSave.add( allocationTokenFlowUtils.redeemToken(allocationToken.get(), domainHistory.createVKey())); } - enqueueTasks(newDomain, hasSignedMarks, hasClaimsNotice); + enqueueTasks(domain, hasSignedMarks, hasClaimsNotice); EntityChanges entityChanges = flowCustomLogic.beforeSave( DomainCreateFlowCustomLogic.BeforeSaveParameters.newBuilder() - .setNewDomain(newDomain) + .setNewDomain(domain) .setHistoryEntry(domainHistory) .setEntityChanges( EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build()) @@ -480,8 +485,8 @@ public class DomainCreateFlow implements TransactionalFlow { : null); } - private DomainHistory buildHistoryEntry( - String repoId, Registry registry, DateTime now, Period period, Duration addGracePeriod) { + private DomainHistory buildDomainHistory( + DomainBase domain, Registry registry, DateTime now, Period period, Duration addGracePeriod) { // We ignore prober transactions if (registry.getTldType() == TldType.REAL) { historyBuilder @@ -497,7 +502,7 @@ public class DomainCreateFlow implements TransactionalFlow { .setType(HistoryEntry.Type.DOMAIN_CREATE) .setPeriod(period) .setModificationTime(now) - .setParent(Key.create(DomainBase.class, repoId)) + .setDomainContent(domain) .build(); } @@ -508,7 +513,7 @@ public class DomainCreateFlow implements TransactionalFlow { boolean isReserved, int years, FeesAndCredits feesAndCredits, - DomainHistory domainHistory, + Key domainHistoryKey, Optional allocationToken, DateTime now) { ImmutableSet.Builder flagsBuilder = new ImmutableSet.Builder<>(); @@ -537,12 +542,12 @@ public class DomainCreateFlow implements TransactionalFlow { ? registry.getAnchorTenantAddGracePeriodLength() : registry.getAddGracePeriodLength())) .setFlags(flagsBuilder.build()) - .setParent(domainHistory) + .setParent(domainHistoryKey) .build(); } private Recurring createAutorenewBillingEvent( - DomainHistory domainHistory, DateTime registrationExpirationTime) { + Key domainHistoryKey, DateTime registrationExpirationTime) { return new BillingEvent.Recurring.Builder() .setReason(Reason.RENEW) .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) @@ -550,18 +555,18 @@ public class DomainCreateFlow implements TransactionalFlow { .setClientId(clientId) .setEventTime(registrationExpirationTime) .setRecurrenceEndTime(END_OF_TIME) - .setParent(domainHistory) + .setParent(domainHistoryKey) .build(); } private Autorenew createAutorenewPollMessage( - HistoryEntry historyEntry, DateTime registrationExpirationTime) { + Key domainHistoryKey, DateTime registrationExpirationTime) { return new PollMessage.Autorenew.Builder() .setTargetId(targetId) .setClientId(clientId) .setEventTime(registrationExpirationTime) .setMsg("Domain was auto-renewed.") - .setParent(historyEntry) + .setParentKey(domainHistoryKey) .build(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java index 4415ffaaa..6d16c027a 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java @@ -16,6 +16,7 @@ package google.registry.flows.domain; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.persistEntityChanges; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; @@ -63,6 +64,7 @@ import google.registry.flows.custom.EntityChanges; import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.Credit; @@ -125,7 +127,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject DnsQueue dnsQueue; @Inject Trid trid; @Inject AsyncTaskEnqueuer asyncTaskEnqueuer; @@ -177,8 +179,8 @@ public final class DomainDeleteFlow implements TransactionalFlow { ? Duration.ZERO // By default, this should be 30 days of grace, and 5 days of pending delete. : redemptionGracePeriodLength.plus(pendingDeleteLength); - HistoryEntry historyEntry = - buildHistoryEntry(existingDomain, registry, now, durationUntilDelete, inAddGracePeriod); + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder.setId(domainHistoryKey.getId()); DateTime deletionTime = now.plus(durationUntilDelete); if (durationUntilDelete.equals(Duration.ZERO)) { builder.setDeletionTime(now).setStatusValues(null); @@ -210,7 +212,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { // it is synchronous). if (durationUntilDelete.isLongerThan(Duration.ZERO) || isSuperuser) { PollMessage.OneTime deletePollMessage = - createDeletePollMessage(existingDomain, historyEntry, deletionTime); + createDeletePollMessage(existingDomain, domainHistoryKey, deletionTime); entitiesToSave.add(deletePollMessage); builder.setDeletePollMessage(deletePollMessage.createVKey()); } @@ -220,7 +222,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { if (durationUntilDelete.isLongerThan(Duration.ZERO) && !clientId.equals(existingDomain.getPersistedCurrentSponsorClientId())) { entitiesToSave.add( - createImmediateDeletePollMessage(existingDomain, historyEntry, now, deletionTime)); + createImmediateDeletePollMessage(existingDomain, domainHistoryKey, now, deletionTime)); } // Cancel any grace periods that were still active, and set the expiration time accordingly. @@ -229,7 +231,7 @@ public final class DomainDeleteFlow implements TransactionalFlow { // No cancellation is written if the grace period was not for a billable event. if (gracePeriod.hasBillingEvent()) { entitiesToSave.add( - BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId)); + BillingEvent.Cancellation.forGracePeriod(gracePeriod, now, domainHistoryKey, targetId)); if (gracePeriod.getOneTimeBillingEvent() != null) { // Take the amount of amount of registration time being refunded off the expiration time. // This can be either add grace periods or renew grace periods. @@ -245,8 +247,10 @@ public final class DomainDeleteFlow implements TransactionalFlow { builder.setRegistrationExpirationTime(newExpirationTime); DomainBase newDomain = builder.build(); + DomainHistory domainHistory = + buildDomainHistory(newDomain, registry, now, durationUntilDelete, inAddGracePeriod); updateForeignKeyIndexDeletionTime(newDomain); - handlePendingTransferOnDelete(existingDomain, newDomain, now, historyEntry); + handlePendingTransferOnDelete(existingDomain, newDomain, now, domainHistory); // Close the autorenew billing event and poll message. This may delete the poll message. updateAutorenewRecurrenceEndTime(existingDomain, now); // If there's a pending transfer, the gaining client's autorenew billing @@ -254,14 +258,16 @@ public final class DomainDeleteFlow implements TransactionalFlow { // ResourceDeleteFlow since it's listed in serverApproveEntities. dnsQueue.addDomainRefreshTask(existingDomain.getDomainName()); - entitiesToSave.add(newDomain, historyEntry); - EntityChanges entityChanges = flowCustomLogic.beforeSave( - BeforeSaveParameters.newBuilder() - .setExistingDomain(existingDomain) - .setNewDomain(newDomain) - .setHistoryEntry(historyEntry) - .setEntityChanges(EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build()) - .build()); + entitiesToSave.add(newDomain, domainHistory); + EntityChanges entityChanges = + flowCustomLogic.beforeSave( + BeforeSaveParameters.newBuilder() + .setExistingDomain(existingDomain) + .setNewDomain(newDomain) + .setHistoryEntry(domainHistory) + .setEntityChanges( + EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build()) + .build()); BeforeResponseReturnData responseData = flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() @@ -292,8 +298,8 @@ public final class DomainDeleteFlow implements TransactionalFlow { } } - private HistoryEntry buildHistoryEntry( - DomainBase existingResource, + private DomainHistory buildDomainHistory( + DomainBase domain, Registry registry, DateTime now, Duration durationUntilDelete, @@ -307,31 +313,30 @@ public final class DomainDeleteFlow implements TransactionalFlow { registry.getRenewGracePeriodLength())); ImmutableSet cancelledRecords = createCancelingRecords( - existingResource, + domain, now, maxGracePeriod, Sets.immutableEnumSet(Sets.union(ADD_FIELDS, RENEW_FIELDS))); - historyBuilder - .setDomainTransactionRecords( - union( - cancelledRecords, - DomainTransactionRecord.create( - existingResource.getTld(), - now.plus(durationUntilDelete), - inAddGracePeriod - ? TransactionReportField.DELETED_DOMAINS_GRACE - : TransactionReportField.DELETED_DOMAINS_NOGRACE, - 1))); + historyBuilder.setDomainTransactionRecords( + union( + cancelledRecords, + DomainTransactionRecord.create( + domain.getTld(), + now.plus(durationUntilDelete), + inAddGracePeriod + ? TransactionReportField.DELETED_DOMAINS_GRACE + : TransactionReportField.DELETED_DOMAINS_NOGRACE, + 1))); } return historyBuilder .setType(HistoryEntry.Type.DOMAIN_DELETE) .setModificationTime(now) - .setParent(Key.create(existingResource)) + .setDomainContent(domain) .build(); } private PollMessage.OneTime createDeletePollMessage( - DomainBase existingDomain, HistoryEntry historyEntry, DateTime deletionTime) { + DomainBase existingDomain, Key domainHistoryKey, DateTime deletionTime) { Optional metadataExtension = eppInput.getSingleExtension(MetadataExtension.class); boolean hasMetadataMessage = @@ -350,16 +355,19 @@ public final class DomainDeleteFlow implements TransactionalFlow { ImmutableList.of( DomainPendingActionNotificationResponse.create( existingDomain.getDomainName(), true, trid, deletionTime))) - .setParent(historyEntry) + .setParentKey(domainHistoryKey) .build(); } private PollMessage.OneTime createImmediateDeletePollMessage( - DomainBase existingDomain, HistoryEntry historyEntry, DateTime now, DateTime deletionTime) { + DomainBase existingDomain, + Key domainHistoryKey, + DateTime now, + DateTime deletionTime) { return new PollMessage.OneTime.Builder() .setClientId(existingDomain.getPersistedCurrentSponsorClientId()) .setEventTime(now) - .setParent(historyEntry) + .setParentKey(domainHistoryKey) .setMsg( String.format( "Domain %s was deleted by registry administrator with final deletion effective: %s", diff --git a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java index 6bdb78f34..08ef87ded 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.persistEntityChanges; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; @@ -33,6 +34,7 @@ import static google.registry.util.DateTimeUtils.leapSafeAddYears; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.EppException.ParameterValueRangeErrorException; import google.registry.flows.ExtensionManager; @@ -52,6 +54,7 @@ import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainCommand.Renew; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.DomainRenewData; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.Period; @@ -123,7 +126,7 @@ public final class DomainRenewFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject DomainRenewFlowCustomLogic flowCustomLogic; @Inject DomainPricingLogic pricingLogic; @@ -156,22 +159,23 @@ public final class DomainRenewFlow implements TransactionalFlow { .setNow(now) .setYears(years) .build()); - Registry registry = Registry.get(existingDomain.getTld()); - HistoryEntry historyEntry = buildHistoryEntry( - existingDomain, now, command.getPeriod(), registry.getRenewGracePeriodLength()); + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder.setId(domainHistoryKey.getId()); String tld = existingDomain.getTld(); // Bill for this explicit renew itself. BillingEvent.OneTime explicitRenewEvent = - createRenewBillingEvent(tld, feesAndCredits.getTotalCost(), years, historyEntry, now); + createRenewBillingEvent(tld, feesAndCredits.getTotalCost(), years, domainHistoryKey, now); // Create a new autorenew billing event and poll message starting at the new expiration time. - BillingEvent.Recurring newAutorenewEvent = newAutorenewBillingEvent(existingDomain) - .setEventTime(newExpirationTime) - .setParent(historyEntry) - .build(); - PollMessage.Autorenew newAutorenewPollMessage = newAutorenewPollMessage(existingDomain) - .setEventTime(newExpirationTime) - .setParent(historyEntry) - .build(); + BillingEvent.Recurring newAutorenewEvent = + newAutorenewBillingEvent(existingDomain) + .setEventTime(newExpirationTime) + .setParent(domainHistoryKey) + .build(); + PollMessage.Autorenew newAutorenewPollMessage = + newAutorenewPollMessage(existingDomain) + .setEventTime(newExpirationTime) + .setParentKey(domainHistoryKey) + .build(); // End the old autorenew billing event and poll message now. This may delete the poll message. updateAutorenewRecurrenceEndTime(existingDomain, now); DomainBase newDomain = @@ -186,6 +190,10 @@ public final class DomainRenewFlow implements TransactionalFlow { GracePeriod.forBillingEvent( GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent)) .build(); + Registry registry = Registry.get(existingDomain.getTld()); + DomainHistory domainHistory = + buildDomainHistory( + newDomain, now, command.getPeriod(), registry.getRenewGracePeriodLength()); EntityChanges entityChanges = flowCustomLogic.beforeSave( BeforeSaveParameters.newBuilder() @@ -193,19 +201,18 @@ public final class DomainRenewFlow implements TransactionalFlow { .setNewDomain(newDomain) .setNow(now) .setYears(years) - .setHistoryEntry(historyEntry) + .setHistoryEntry(domainHistory) .setEntityChanges( EntityChanges.newBuilder() .setSaves( ImmutableSet.of( newDomain, - historyEntry, + domainHistory, explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage)) .build()) .build()); - persistEntityChanges(entityChanges); BeforeResponseReturnData responseData = flowCustomLogic.beforeResponse( BeforeResponseParameters.newBuilder() @@ -213,23 +220,24 @@ public final class DomainRenewFlow implements TransactionalFlow { .setResData(DomainRenewData.create(targetId, newExpirationTime)) .setResponseExtensions(createResponseExtensions(feesAndCredits, feeRenew)) .build()); + persistEntityChanges(entityChanges); return responseBuilder .setResData(responseData.resData()) .setExtensions(responseData.responseExtensions()) .build(); } - private HistoryEntry buildHistoryEntry( - DomainBase existingDomain, DateTime now, Period period, Duration renewGracePeriod) { + private DomainHistory buildDomainHistory( + DomainBase newDomain, DateTime now, Period period, Duration renewGracePeriod) { return historyBuilder .setType(HistoryEntry.Type.DOMAIN_RENEW) .setPeriod(period) .setModificationTime(now) - .setParent(existingDomain) + .setDomainContent(newDomain) .setDomainTransactionRecords( ImmutableSet.of( DomainTransactionRecord.create( - existingDomain.getTld(), + newDomain.getTld(), now.plus(renewGracePeriod), TransactionReportField.netRenewsFieldFromYears(period.getValue()), 1))) @@ -255,7 +263,7 @@ public final class DomainRenewFlow implements TransactionalFlow { } private OneTime createRenewBillingEvent( - String tld, Money renewCost, int years, HistoryEntry historyEntry, DateTime now) { + String tld, Money renewCost, int years, Key domainHistoryKey, DateTime now) { return new BillingEvent.OneTime.Builder() .setReason(Reason.RENEW) .setTargetId(targetId) @@ -264,7 +272,7 @@ public final class DomainRenewFlow implements TransactionalFlow { .setCost(renewCost) .setEventTime(now) .setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength())) - .setParent(historyEntry) + .setParent(domainHistoryKey) .build(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index 38c4232f5..855386987 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo; @@ -49,6 +50,7 @@ import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainCommand.Update; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.Fee; import google.registry.model.domain.fee.FeeTransformResponseExtension; @@ -117,7 +119,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject DnsQueue dnsQueue; @Inject EppResponse.Builder responseBuilder; @Inject DomainPricingLogic pricingLogic; @@ -142,7 +144,8 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { Optional feeUpdate = eppInput.getSingleExtension(FeeUpdateCommandExtension.class); verifyRestoreAllowed(command, existingDomain, feeUpdate, feesAndCredits, now); - HistoryEntry historyEntry = buildHistoryEntry(existingDomain, now); + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder.setId(domainHistoryKey.getId()); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); DateTime newExpirationTime = @@ -150,29 +153,31 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { // Restore the expiration time on the deleted domain, except if that's already passed, then add // a year and bill for it immediately, with no grace period. if (isExpired) { - entitiesToSave.add(createRenewBillingEvent(historyEntry, feesAndCredits.getRenewCost(), now)); + entitiesToSave.add( + createRenewBillingEvent(domainHistoryKey, feesAndCredits.getRenewCost(), now)); } // Always bill for the restore itself. entitiesToSave.add( - createRestoreBillingEvent(historyEntry, feesAndCredits.getRestoreCost(), now)); + createRestoreBillingEvent(domainHistoryKey, feesAndCredits.getRestoreCost(), now)); BillingEvent.Recurring autorenewEvent = newAutorenewBillingEvent(existingDomain) .setEventTime(newExpirationTime) .setRecurrenceEndTime(END_OF_TIME) - .setParent(historyEntry) + .setParent(domainHistoryKey) .build(); PollMessage.Autorenew autorenewPollMessage = newAutorenewPollMessage(existingDomain) .setEventTime(newExpirationTime) .setAutorenewEndTime(END_OF_TIME) - .setParent(historyEntry) + .setParentKey(domainHistoryKey) .build(); DomainBase newDomain = performRestore( existingDomain, newExpirationTime, autorenewEvent, autorenewPollMessage, now, clientId); updateForeignKeyIndexDeletionTime(newDomain); - entitiesToSave.add(newDomain, historyEntry, autorenewEvent, autorenewPollMessage); + DomainHistory domainHistory = buildDomainHistory(newDomain, now); + entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage); tm().putAll(entitiesToSave.build()); tm().delete(existingDomain.getDeletePollMessage()); dnsQueue.addDomainRefreshTask(existingDomain.getDomainName()); @@ -181,15 +186,15 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { .build(); } - private HistoryEntry buildHistoryEntry(DomainBase existingDomain, DateTime now) { + private DomainHistory buildDomainHistory(DomainBase newDomain, DateTime now) { return historyBuilder .setType(HistoryEntry.Type.DOMAIN_RESTORE) .setModificationTime(now) - .setParent(Key.create(existingDomain)) + .setDomainContent(newDomain) .setDomainTransactionRecords( ImmutableSet.of( DomainTransactionRecord.create( - existingDomain.getTld(), now, TransactionReportField.RESTORED_DOMAINS, 1))) + newDomain.getTld(), now, TransactionReportField.RESTORED_DOMAINS, 1))) .build(); } @@ -242,20 +247,19 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { } private OneTime createRenewBillingEvent( - HistoryEntry historyEntry, Money renewCost, DateTime now) { - return prepareBillingEvent(historyEntry, renewCost, now) - .setReason(Reason.RENEW) - .build(); + Key domainHistoryKey, Money renewCost, DateTime now) { + return prepareBillingEvent(domainHistoryKey, renewCost, now).setReason(Reason.RENEW).build(); } private BillingEvent.OneTime createRestoreBillingEvent( - HistoryEntry historyEntry, Money restoreCost, DateTime now) { - return prepareBillingEvent(historyEntry, restoreCost, now) + Key domainHistoryKey, Money restoreCost, DateTime now) { + return prepareBillingEvent(domainHistoryKey, restoreCost, now) .setReason(Reason.RESTORE) .build(); } - private OneTime.Builder prepareBillingEvent(HistoryEntry historyEntry, Money cost, DateTime now) { + private OneTime.Builder prepareBillingEvent( + Key domainHistoryKey, Money cost, DateTime now) { return new BillingEvent.OneTime.Builder() .setTargetId(targetId) .setClientId(clientId) @@ -263,7 +267,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { .setBillingTime(now) .setPeriodYears(1) .setCost(cost) - .setParent(historyEntry); + .setParent(domainHistoryKey); } private static ImmutableList createResponseExtensions( diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java index 0edc3471e..65590bbcf 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java @@ -15,6 +15,7 @@ package google.registry.flows.domain; import static com.google.common.collect.Iterables.getOnlyElement; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.computeExDateForApprovalTime; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; @@ -48,6 +49,7 @@ import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.rgp.GracePeriodStatus; @@ -90,7 +92,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject DomainTransferApproveFlow() {} @@ -114,10 +116,10 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { } DomainTransferData transferData = existingDomain.getTransferData(); String gainingClientId = transferData.getGainingClientId(); - Registry registry = Registry.get(existingDomain.getTld()); - HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now, gainingClientId); // Create a transfer billing event for 1 year, unless the superuser extension was used to set // the transfer period to zero. There is not a transfer cost if the transfer period is zero. + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder.setId(domainHistoryKey.getId()); Optional billingEvent = (transferData.getTransferPeriod().getValue() == 0) ? Optional.empty() @@ -130,10 +132,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .setCost(getDomainRenewCost(targetId, transferData.getTransferRequestTime(), 1)) .setEventTime(now) .setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength())) - .setParent(historyEntry) + .setParent(domainHistoryKey) .build()); ImmutableList.Builder entitiesToSave = new ImmutableList.Builder<>(); - entitiesToSave.add(historyEntry); // If we are within an autorenew grace period, cancel the autorenew billing event and don't // increase the registration time, since the transfer subsumes the autorenew's extra year. GracePeriod autorenewGrace = @@ -146,7 +147,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { // still needs to be charged for the auto-renew. if (billingEvent.isPresent()) { entitiesToSave.add( - BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId)); + BillingEvent.Cancellation.forGracePeriod( + autorenewGrace, now, domainHistoryKey, targetId)); } } // Close the old autorenew event and poll message at the transfer time (aka now). This may end @@ -155,24 +157,26 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { DateTime newExpirationTime = computeExDateForApprovalTime(existingDomain, now, transferData.getTransferPeriod()); // Create a new autorenew event starting at the expiration time. - BillingEvent.Recurring autorenewEvent = new BillingEvent.Recurring.Builder() - .setReason(Reason.RENEW) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) - .setTargetId(targetId) - .setClientId(gainingClientId) - .setEventTime(newExpirationTime) - .setRecurrenceEndTime(END_OF_TIME) - .setParent(historyEntry) - .build(); + BillingEvent.Recurring autorenewEvent = + new BillingEvent.Recurring.Builder() + .setReason(Reason.RENEW) + .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) + .setTargetId(targetId) + .setClientId(gainingClientId) + .setEventTime(newExpirationTime) + .setRecurrenceEndTime(END_OF_TIME) + .setParent(domainHistoryKey) + .build(); // Create a new autorenew poll message. - PollMessage.Autorenew gainingClientAutorenewPollMessage = new PollMessage.Autorenew.Builder() - .setTargetId(targetId) - .setClientId(gainingClientId) - .setEventTime(newExpirationTime) - .setAutorenewEndTime(END_OF_TIME) - .setMsg("Domain was auto-renewed.") - .setParent(historyEntry) - .build(); + PollMessage.Autorenew gainingClientAutorenewPollMessage = + new PollMessage.Autorenew.Builder() + .setTargetId(targetId) + .setClientId(gainingClientId) + .setEventTime(newExpirationTime) + .setAutorenewEndTime(END_OF_TIME) + .setMsg("Domain was auto-renewed.") + .setParentKey(domainHistoryKey) + .build(); // Construct the post-transfer domain. DomainBase partiallyApprovedDomain = approvePendingTransfer(existingDomain, TransferStatus.CLIENT_APPROVED, now); @@ -204,13 +208,19 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .setLastEppUpdateTime(now) .setLastEppUpdateClientId(clientId) .build(); + Registry registry = Registry.get(existingDomain.getTld()); + DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now, gainingClientId); // Create a poll message for the gaining client. PollMessage gainingClientPollMessage = createGainingTransferPollMessage( - targetId, newDomain.getTransferData(), newExpirationTime, historyEntry); + targetId, newDomain.getTransferData(), newExpirationTime, now, domainHistoryKey); billingEvent.ifPresent(entitiesToSave::add); entitiesToSave.add( - autorenewEvent, gainingClientPollMessage, gainingClientAutorenewPollMessage, newDomain); + autorenewEvent, + gainingClientPollMessage, + gainingClientAutorenewPollMessage, + newDomain, + domainHistory); tm().putAll(entitiesToSave.build()); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. @@ -221,11 +231,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .build(); } - private HistoryEntry buildHistoryEntry( - DomainBase existingDomain, Registry registry, DateTime now, String gainingClientId) { + private DomainHistory buildDomainHistory( + DomainBase newDomain, Registry registry, DateTime now, String gainingClientId) { ImmutableSet cancelingRecords = createCancelingRecords( - existingDomain, + newDomain, now, registry.getAutomaticTransferLength().plus(registry.getTransferGracePeriodLength()), ImmutableSet.of(TRANSFER_SUCCESSFUL)); @@ -233,15 +243,15 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .setType(HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE) .setModificationTime(now) .setOtherClientId(gainingClientId) - .setParent(Key.create(existingDomain)) + .setDomainContent(newDomain) .setDomainTransactionRecords( union( cancelingRecords, DomainTransactionRecord.create( - existingDomain.getTld(), - now.plus(registry.getTransferGracePeriodLength()), - TRANSFER_SUCCESSFUL, - 1))) + newDomain.getTld(), + now.plus(registry.getTransferGracePeriodLength()), + TRANSFER_SUCCESSFUL, + 1))) .build(); } } diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java index d2b504534..b5ef05258 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer; @@ -39,6 +40,7 @@ import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppoutput.EppResponse; @@ -77,7 +79,7 @@ public final class DomainTransferCancelFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject DomainTransferCancelFlow() {} @@ -95,14 +97,20 @@ public final class DomainTransferCancelFlow implements TransactionalFlow { checkAllowedAccessToTld(clientId, existingDomain.getTld()); } Registry registry = Registry.get(existingDomain.getTld()); - HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now); + + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder + .setId(domainHistoryKey.getId()) + .setOtherClientId(existingDomain.getTransferData().getLosingClientId()); + DomainBase newDomain = denyPendingTransfer(existingDomain, TransferStatus.CLIENT_CANCELLED, now, clientId); + DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now); tm().putAll( newDomain, - historyEntry, + domainHistory, createLosingTransferPollMessage( - targetId, newDomain.getTransferData(), null, historyEntry)); + targetId, newDomain.getTransferData(), null, domainHistoryKey)); // Reopen the autorenew event and poll message that we closed for the implicit transfer. This // may recreate the autorenew poll message if it was deleted when the transfer request was made. updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME); @@ -114,19 +122,17 @@ public final class DomainTransferCancelFlow implements TransactionalFlow { .build(); } - private HistoryEntry buildHistoryEntry( - DomainBase existingDomain, Registry registry, DateTime now) { + private DomainHistory buildDomainHistory(DomainBase newDomain, Registry registry, DateTime now) { ImmutableSet cancelingRecords = createCancelingRecords( - existingDomain, + newDomain, now, registry.getAutomaticTransferLength().plus(registry.getTransferGracePeriodLength()), ImmutableSet.of(TRANSFER_SUCCESSFUL)); return historyBuilder .setType(HistoryEntry.Type.DOMAIN_TRANSFER_CANCEL) - .setOtherClientId(existingDomain.getTransferData().getLosingClientId()) .setModificationTime(now) - .setParent(Key.create(existingDomain)) + .setDomainContent(newDomain) .setDomainTransactionRecords(cancelingRecords) .build(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java index cdc116263..1c8160028 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer; @@ -41,6 +42,7 @@ import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppoutput.EppResponse; @@ -79,7 +81,7 @@ public final class DomainTransferRejectFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject DomainTransferRejectFlow() {} @@ -91,7 +93,11 @@ public final class DomainTransferRejectFlow implements TransactionalFlow { DateTime now = tm().getTransactionTime(); DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now); Registry registry = Registry.get(existingDomain.getTld()); - HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now); + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder + .setId(domainHistoryKey.getId()) + .setOtherClientId(existingDomain.getTransferData().getGainingClientId()); + verifyOptionalAuthInfo(authInfo, existingDomain); verifyHasPendingTransfer(existingDomain); verifyResourceOwnership(clientId, existingDomain); @@ -100,11 +106,12 @@ public final class DomainTransferRejectFlow implements TransactionalFlow { } DomainBase newDomain = denyPendingTransfer(existingDomain, TransferStatus.CLIENT_REJECTED, now, clientId); + DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now); tm().putAll( newDomain, - historyEntry, + domainHistory, createGainingTransferPollMessage( - targetId, newDomain.getTransferData(), null, historyEntry)); + targetId, newDomain.getTransferData(), null, now, domainHistoryKey)); // Reopen the autorenew event and poll message that we closed for the implicit transfer. This // may end up recreating the poll message if it was deleted upon the transfer request. updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME); @@ -116,24 +123,21 @@ public final class DomainTransferRejectFlow implements TransactionalFlow { .build(); } - private HistoryEntry buildHistoryEntry( - DomainBase existingDomain, Registry registry, DateTime now) { + private DomainHistory buildDomainHistory(DomainBase newDomain, Registry registry, DateTime now) { ImmutableSet cancelingRecords = createCancelingRecords( - existingDomain, + newDomain, now, registry.getAutomaticTransferLength().plus(registry.getTransferGracePeriodLength()), ImmutableSet.of(TRANSFER_SUCCESSFUL)); return historyBuilder .setType(HistoryEntry.Type.DOMAIN_TRANSFER_REJECT) .setModificationTime(now) - .setOtherClientId(existingDomain.getTransferData().getGainingClientId()) - .setParent(Key.create(existingDomain)) .setDomainTransactionRecords( union( cancelingRecords, - DomainTransactionRecord.create( - existingDomain.getTld(), now, TRANSFER_NACKED, 1))) + DomainTransactionRecord.create(newDomain.getTld(), now, TRANSFER_NACKED, 1))) + .setDomainContent(newDomain) .build(); } } diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java index aaa3f5473..509c4f0bf 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static google.registry.flows.FlowUtils.createHistoryKey; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.computeExDateForApprovalTime; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; @@ -51,6 +52,7 @@ import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException; import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException; import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainCommand.Transfer; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.Period; import google.registry.model.domain.fee.FeeTransferCommandExtension; import google.registry.model.domain.fee.FeeTransformResponseExtension; @@ -126,7 +128,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { @Inject @ClientId String gainingClientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject Trid trid; @Inject AsyncTaskEnqueuer asyncTaskEnqueuer; @Inject EppResponse.Builder responseBuilder; @@ -169,7 +171,10 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { if (feesAndCredits.isPresent()) { validateFeeChallenge(targetId, now, feeTransfer, feesAndCredits.get()); } - HistoryEntry historyEntry = buildHistoryEntry(existingDomain, registry, now, period); + Key domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class); + historyBuilder + .setId(domainHistoryKey.getId()) + .setOtherClientId(existingDomain.getCurrentSponsorClientId()); DateTime automaticTransferTime = superuserExtension.isPresent() ? now.plusDays(superuserExtension.get().getAutomaticTransferLength()) @@ -190,7 +195,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { createTransferServerApproveEntities( automaticTransferTime, serverApproveNewExpirationTime, - historyEntry, + domainHistoryKey, existingDomain, trid, gainingClientId, @@ -209,9 +214,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { serverApproveEntities, period); // Create a poll message to notify the losing registrar that a transfer was requested. - PollMessage requestPollMessage = createLosingTransferPollMessage( - targetId, pendingTransferData, serverApproveNewExpirationTime, historyEntry) - .asBuilder().setEventTime(now).build(); + PollMessage requestPollMessage = + createLosingTransferPollMessage( + targetId, pendingTransferData, serverApproveNewExpirationTime, domainHistoryKey) + .asBuilder() + .setEventTime(now) + .build(); // End the old autorenew event and poll message at the implicit transfer time. This may delete // the poll message if it has no events left. Note that if the automatic transfer succeeds, then // cloneProjectedAtTime() will replace these old autorenew entities with the server approve ones @@ -225,10 +233,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { .setLastEppUpdateTime(now) .setLastEppUpdateClientId(gainingClientId) .build(); + DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now, period); + asyncTaskEnqueuer.enqueueAsyncResave(newDomain, now, automaticTransferTime); tm().putAll( new ImmutableSet.Builder<>() - .add(newDomain, historyEntry, requestPollMessage) + .add(newDomain, domainHistory, requestPollMessage) .addAll(serverApproveEntities) .build()); return responseBuilder @@ -302,14 +312,13 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { } } - private HistoryEntry buildHistoryEntry( - DomainBase existingDomain, Registry registry, DateTime now, Period period) { + private DomainHistory buildDomainHistory( + DomainBase newDomain, Registry registry, DateTime now, Period period) { return historyBuilder .setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST) - .setOtherClientId(existingDomain.getCurrentSponsorClientId()) .setPeriod(period) .setModificationTime(now) - .setParent(Key.create(existingDomain)) + .setDomainContent(newDomain) .setDomainTransactionRecords( ImmutableSet.of( DomainTransactionRecord.create( diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java b/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java index c1e935a22..24c41b400 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferUtils.java @@ -20,10 +20,12 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.googlecode.objectify.Key; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.Period; import google.registry.model.domain.rgp.GracePeriodStatus; @@ -31,7 +33,6 @@ import google.registry.model.eppcommon.Trid; import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse; import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; -import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferData.TransferServerApproveEntity; @@ -90,20 +91,21 @@ public final class DomainTransferUtils { * Returns a set of entities created speculatively in anticipation of a server approval. * *

This set consists of: + * *

    - *
  • The one-time billing event charging the gaining registrar for the transfer - *
  • A cancellation of an autorenew charge for the losing registrar, if the autorenew grace - * period will apply at transfer time - *
  • A new post-transfer autorenew billing event for the domain (and gaining registrar) - *
  • A new post-transfer autorenew poll message for the domain (and gaining registrar) - *
  • A poll message for the gaining registrar - *
  • A poll message for the losing registrar + *
  • The one-time billing event charging the gaining registrar for the transfer + *
  • A cancellation of an autorenew charge for the losing registrar, if the autorenew grace + * period will apply at transfer time + *
  • A new post-transfer autorenew billing event for the domain (and gaining registrar) + *
  • A new post-transfer autorenew poll message for the domain (and gaining registrar) + *
  • A poll message for the gaining registrar + *
  • A poll message for the losing registrar *
*/ public static ImmutableSet createTransferServerApproveEntities( DateTime automaticTransferTime, DateTime serverApproveNewExpirationTime, - HistoryEntry historyEntry, + Key domainHistoryKey, DomainBase existingDomain, Trid trid, String gainingClientId, @@ -123,32 +125,39 @@ public final class DomainTransferUtils { .build(); Registry registry = Registry.get(existingDomain.getTld()); ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); - if (transferCost.isPresent()) { - builder.add( - createTransferBillingEvent( - automaticTransferTime, - historyEntry, - targetId, - gainingClientId, - registry, - transferCost.get())); - } + transferCost.ifPresent( + cost -> + builder.add( + createTransferBillingEvent( + automaticTransferTime, + domainHistoryKey, + targetId, + gainingClientId, + registry, + cost))); createOptionalAutorenewCancellation( - automaticTransferTime, historyEntry, targetId, existingDomain, transferCost) + automaticTransferTime, now, domainHistoryKey, targetId, existingDomain, transferCost) .ifPresent(builder::add); return builder .add( createGainingClientAutorenewEvent( - serverApproveNewExpirationTime, historyEntry, targetId, gainingClientId)) + serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingClientId)) .add( createGainingClientAutorenewPollMessage( - serverApproveNewExpirationTime, historyEntry, targetId, gainingClientId)) + serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingClientId)) .add( createGainingTransferPollMessage( - targetId, serverApproveTransferData, serverApproveNewExpirationTime, historyEntry)) + targetId, + serverApproveTransferData, + serverApproveNewExpirationTime, + now, + domainHistoryKey)) .add( createLosingTransferPollMessage( - targetId, serverApproveTransferData, serverApproveNewExpirationTime, historyEntry)) + targetId, + serverApproveTransferData, + serverApproveNewExpirationTime, + domainHistoryKey)) .build(); } @@ -157,19 +166,21 @@ public final class DomainTransferUtils { String targetId, TransferData transferData, @Nullable DateTime extendedRegistrationExpirationTime, - HistoryEntry historyEntry) { + DateTime now, + Key domainHistoryKey) { return new PollMessage.OneTime.Builder() .setClientId(transferData.getGainingClientId()) .setEventTime(transferData.getPendingTransferExpirationTime()) .setMsg(transferData.getTransferStatus().getMessage()) - .setResponseData(ImmutableList.of( - createTransferResponse(targetId, transferData, extendedRegistrationExpirationTime), - DomainPendingActionNotificationResponse.create( - targetId, - transferData.getTransferStatus().isApproved(), - transferData.getTransferRequestTrid(), - historyEntry.getModificationTime()))) - .setParent(historyEntry) + .setResponseData( + ImmutableList.of( + createTransferResponse(targetId, transferData, extendedRegistrationExpirationTime), + DomainPendingActionNotificationResponse.create( + targetId, + transferData.getTransferStatus().isApproved(), + transferData.getTransferRequestTrid(), + now))) + .setParentKey(domainHistoryKey) .build(); } @@ -178,14 +189,15 @@ public final class DomainTransferUtils { String targetId, TransferData transferData, @Nullable DateTime extendedRegistrationExpirationTime, - HistoryEntry historyEntry) { + Key domainHistoryKey) { return new PollMessage.OneTime.Builder() .setClientId(transferData.getLosingClientId()) .setEventTime(transferData.getPendingTransferExpirationTime()) .setMsg(transferData.getTransferStatus().getMessage()) - .setResponseData(ImmutableList.of( - createTransferResponse(targetId, transferData, extendedRegistrationExpirationTime))) - .setParent(historyEntry) + .setResponseData( + ImmutableList.of( + createTransferResponse(targetId, transferData, extendedRegistrationExpirationTime))) + .setParentKey(domainHistoryKey) .build(); } @@ -207,7 +219,7 @@ public final class DomainTransferUtils { private static PollMessage.Autorenew createGainingClientAutorenewPollMessage( DateTime serverApproveNewExpirationTime, - HistoryEntry historyEntry, + Key domainHistoryKey, String targetId, String gainingClientId) { return new PollMessage.Autorenew.Builder() @@ -216,13 +228,13 @@ public final class DomainTransferUtils { .setEventTime(serverApproveNewExpirationTime) .setAutorenewEndTime(END_OF_TIME) .setMsg("Domain was auto-renewed.") - .setParent(historyEntry) + .setParentKey(domainHistoryKey) .build(); } private static BillingEvent.Recurring createGainingClientAutorenewEvent( DateTime serverApproveNewExpirationTime, - HistoryEntry historyEntry, + Key domainHistoryKey, String targetId, String gainingClientId) { return new BillingEvent.Recurring.Builder() @@ -232,7 +244,7 @@ public final class DomainTransferUtils { .setClientId(gainingClientId) .setEventTime(serverApproveNewExpirationTime) .setRecurrenceEndTime(END_OF_TIME) - .setParent(historyEntry) + .setParent(domainHistoryKey) .build(); } @@ -254,7 +266,8 @@ public final class DomainTransferUtils { */ private static Optional createOptionalAutorenewCancellation( DateTime automaticTransferTime, - HistoryEntry historyEntry, + DateTime now, + Key domainHistoryKey, String targetId, DomainBase existingDomain, Optional transferCost) { @@ -265,7 +278,8 @@ public final class DomainTransferUtils { domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null); if (autorenewGracePeriod != null && transferCost.isPresent()) { return Optional.of( - BillingEvent.Cancellation.forGracePeriod(autorenewGracePeriod, historyEntry, targetId) + BillingEvent.Cancellation.forGracePeriod( + autorenewGracePeriod, now, domainHistoryKey, targetId) .asBuilder() .setEventTime(automaticTransferTime) .build()); @@ -275,7 +289,7 @@ public final class DomainTransferUtils { private static BillingEvent.OneTime createTransferBillingEvent( DateTime automaticTransferTime, - HistoryEntry historyEntry, + Key domainHistoryKey, String targetId, String gainingClientId, Registry registry, @@ -288,7 +302,7 @@ public final class DomainTransferUtils { .setPeriodYears(1) .setEventTime(automaticTransferTime) .setBillingTime(automaticTransferTime.plus(registry.getTransferGracePeriodLength())) - .setParent(historyEntry) + .setParent(domainHistoryKey) .build(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java index 142306c03..cb238edcf 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -43,7 +43,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import com.google.common.collect.ImmutableSet; import com.google.common.net.InternetDomainName; -import com.googlecode.objectify.Key; import google.registry.dns.DnsQueue; import google.registry.flows.EppException; import google.registry.flows.ExtensionManager; @@ -65,6 +64,7 @@ import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainCommand.Update; import google.registry.model.domain.DomainCommand.Update.AddRemove; import google.registry.model.domain.DomainCommand.Update.Change; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.fee.FeeUpdateCommandExtension; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.secdns.DelegationSignerData; @@ -142,7 +142,7 @@ public final class DomainUpdateFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject DomainHistory.Builder historyBuilder; @Inject DnsQueue dnsQueue; @Inject EppResponse.Builder responseBuilder; @Inject DomainUpdateFlowCustomLogic flowCustomLogic; @@ -165,19 +165,19 @@ public final class DomainUpdateFlow implements TransactionalFlow { verifyUpdateAllowed(command, existingDomain, now); flowCustomLogic.afterValidation( AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build()); - HistoryEntry historyEntry = buildHistoryEntry(existingDomain, now); DomainBase newDomain = performUpdate(command, existingDomain, now); + DomainHistory domainHistory = buildDomainHistory(newDomain, now); validateNewState(newDomain); dnsQueue.addDomainRefreshTask(targetId); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); - entitiesToSave.add(newDomain, historyEntry); + entitiesToSave.add(newDomain, domainHistory); Optional statusUpdateBillingEvent = - createBillingEventForStatusUpdates(existingDomain, newDomain, historyEntry, now); + createBillingEventForStatusUpdates(existingDomain, newDomain, domainHistory, now); statusUpdateBillingEvent.ifPresent(entitiesToSave::add); EntityChanges entityChanges = flowCustomLogic.beforeSave( BeforeSaveParameters.newBuilder() - .setHistoryEntry(historyEntry) + .setHistoryEntry(domainHistory) .setNewDomain(newDomain) .setExistingDomain(existingDomain) .setEntityChanges( @@ -217,11 +217,11 @@ public final class DomainUpdateFlow implements TransactionalFlow { tld, add.getNameserverFullyQualifiedHostNames()); } - private HistoryEntry buildHistoryEntry(DomainBase existingDomain, DateTime now) { + private DomainHistory buildDomainHistory(DomainBase newDomain, DateTime now) { return historyBuilder .setType(HistoryEntry.Type.DOMAIN_UPDATE) .setModificationTime(now) - .setParent(Key.create(existingDomain)) + .setDomainContent(newDomain) .build(); } diff --git a/core/src/main/java/google/registry/model/billing/BillingEvent.java b/core/src/main/java/google/registry/model/billing/BillingEvent.java index d4017cf11..fcf3ed42a 100644 --- a/core/src/main/java/google/registry/model/billing/BillingEvent.java +++ b/core/src/main/java/google/registry/model/billing/BillingEvent.java @@ -42,6 +42,7 @@ import google.registry.model.ImmutableObject; import google.registry.model.annotations.ReportedOn; import google.registry.model.common.TimeOfYear; import google.registry.model.domain.DomainBase; +import google.registry.model.domain.DomainHistory; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.token.AllocationToken; @@ -114,7 +115,7 @@ public abstract class BillingEvent extends ImmutableObject /** Entity id. */ @Id @javax.persistence.Id Long id; - @Parent @DoNotHydrate @Transient Key parent; + @Parent @DoNotHydrate @Transient Key parent; /** The registrar to bill. */ @Index @@ -191,7 +192,7 @@ public abstract class BillingEvent extends ImmutableObject return targetId; } - public Key getParentKey() { + public Key getParentKey() { return parent; } @@ -258,7 +259,7 @@ public abstract class BillingEvent extends ImmutableObject return thisCastToDerived(); } - public B setParent(Key parentKey) { + public B setParent(Key parentKey) { getInstance().parent = parentKey; return thisCastToDerived(); } @@ -602,23 +603,27 @@ public abstract class BillingEvent extends ImmutableObject GracePeriodStatus.TRANSFER, Reason.TRANSFER); /** - * Creates a cancellation billing event (parented on the provided history entry, and with the - * history entry's event time) that will cancel out the provided grace period's billing event, + * Creates a cancellation billing event (parented on the provided history key, and with the + * corresponding event time) that will cancel out the provided grace period's billing event, * using the supplied targetId and deriving other metadata (clientId, billing time, and the * cancellation reason) from the grace period. */ public static BillingEvent.Cancellation forGracePeriod( - GracePeriod gracePeriod, HistoryEntry historyEntry, String targetId) { + GracePeriod gracePeriod, + DateTime eventTime, + Key domainHistoryKey, + String targetId) { checkArgument(gracePeriod.hasBillingEvent(), "Cannot create cancellation for grace period without billing event"); - BillingEvent.Cancellation.Builder builder = new BillingEvent.Cancellation.Builder() - .setReason(checkNotNull(GRACE_PERIOD_TO_REASON.get(gracePeriod.getType()))) - .setTargetId(targetId) - .setClientId(gracePeriod.getClientId()) - .setEventTime(historyEntry.getModificationTime()) - // The charge being cancelled will take place at the grace period's expiration time. - .setBillingTime(gracePeriod.getExpirationTime()) - .setParent(historyEntry); + BillingEvent.Cancellation.Builder builder = + new BillingEvent.Cancellation.Builder() + .setReason(checkNotNull(GRACE_PERIOD_TO_REASON.get(gracePeriod.getType()))) + .setTargetId(targetId) + .setClientId(gracePeriod.getClientId()) + .setEventTime(eventTime) + // The charge being cancelled will take place at the grace period's expiration time. + .setBillingTime(gracePeriod.getExpirationTime()) + .setParent(domainHistoryKey); // Set the grace period's billing event using the appropriate Cancellation builder method. if (gracePeriod.getOneTimeBillingEvent() != null) { builder.setOneTimeEventKey(gracePeriod.getOneTimeBillingEvent()); diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java index 9fe12542f..f390a56d2 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java @@ -377,12 +377,16 @@ public class HistoryEntry extends ImmutableObject implements Buildable, Datastor return thisCastToDerived(); } + public B copyFrom(HistoryEntry.Builder builder) { + return copyFrom(builder.getInstance()); + } + @Override public T build() { return super.build(); } - public B setId(long id) { + public B setId(Long id) { getInstance().id = id; return thisCastToDerived(); } diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java index 3319ef6cb..b6980f3cf 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java @@ -22,7 +22,7 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.Iterables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Streams; import google.registry.model.EppResource; import google.registry.model.contact.ContactHistory; @@ -34,6 +34,7 @@ import google.registry.model.host.HostResource; import google.registry.persistence.VKey; import google.registry.persistence.transaction.CriteriaQueryBuilder; import java.util.Comparator; +import java.util.List; import java.util.stream.Stream; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -48,7 +49,7 @@ import org.joda.time.DateTime; public class HistoryEntryDao { /** Loads all history objects in the times specified, including all types. */ - public static Iterable loadAllHistoryObjects( + public static ImmutableList loadAllHistoryObjects( DateTime afterTime, DateTime beforeTime) { if (tm().isOfy()) { return Streams.stream( @@ -64,21 +65,25 @@ public class HistoryEntryDao { return jpaTm() .transact( () -> - Iterables.concat( - loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime), - loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime), - loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime))); + new ImmutableList.Builder() + .addAll( + loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime)) + .addAll( + loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime)) + .addAll( + loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime)) + .build()); } } /** Loads all history objects corresponding to the given {@link EppResource}. */ - public static Iterable loadHistoryObjectsForResource( + public static ImmutableList loadHistoryObjectsForResource( VKey parentKey) { return loadHistoryObjectsForResource(parentKey, START_OF_TIME, END_OF_TIME); } /** Loads all history objects in the time period specified for the given {@link EppResource}. */ - public static Iterable loadHistoryObjectsForResource( + public static ImmutableList loadHistoryObjectsForResource( VKey parentKey, DateTime afterTime, DateTime beforeTime) { if (tm().isOfy()) { return Streams.stream( @@ -130,7 +135,7 @@ public class HistoryEntryDao { .getResultStream(); } - private static Iterable loadHistoryObjectsForResourceFromSql( + private static ImmutableList loadHistoryObjectsForResourceFromSql( VKey parentKey, DateTime afterTime, DateTime beforeTime) { // The class we're searching from is based on which parent type (e.g. Domain) we have Class historyClass = getHistoryClassFromParent(parentKey.getKind()); @@ -144,12 +149,9 @@ public class HistoryEntryDao { .where(repoIdFieldName, criteriaBuilder::equal, parentKey.getSqlKey().toString()) .build(); - return jpaTm() - .getEntityManager() - .createQuery(criteriaQuery) - .getResultStream() - .sorted(Comparator.comparing(HistoryEntry::getModificationTime)) - .collect(toImmutableList()); + return ImmutableList.sortedCopyOf( + Comparator.comparing(HistoryEntry::getModificationTime), + jpaTm().getEntityManager().createQuery(criteriaQuery).getResultList()); } private static Class getHistoryClassFromParent( @@ -172,7 +174,7 @@ public class HistoryEntryDao { : historyClass.equals(DomainHistory.class) ? "domainRepoId" : "hostRepoId"; } - private static Iterable loadAllHistoryObjectsFromSql( + private static List loadAllHistoryObjectsFromSql( Class historyClass, DateTime afterTime, DateTime beforeTime) { CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder(); return jpaTm() diff --git a/core/src/main/java/google/registry/model/transfer/DomainTransferData.java b/core/src/main/java/google/registry/model/transfer/DomainTransferData.java index 1fbbed14c..397746243 100644 --- a/core/src/main/java/google/registry/model/transfer/DomainTransferData.java +++ b/core/src/main/java/google/registry/model/transfer/DomainTransferData.java @@ -164,6 +164,7 @@ public class DomainTransferData extends TransferData serverApproveEntities = null; postLoad(); } + hashCode = null; // reset the hash code since we may have changed the entities } /** @@ -271,6 +272,7 @@ public class DomainTransferData extends TransferData serverApproveEntitiesBuilder.add(billingCancellationId); } serverApproveEntities = forceEmptyToNull(serverApproveEntitiesBuilder.build()); + hashCode = null; // reset the hash code since we may have changed the entities } @Override diff --git a/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java b/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java index c8318166e..7a9f32a55 100644 --- a/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java +++ b/core/src/test/java/google/registry/flows/EppLifecycleDomainTest.java @@ -757,11 +757,11 @@ class EppLifecycleDomainTest extends EppTestCase { .hasResponse( "poll_response_autorenew.xml", ImmutableMap.of( - "ID", "1-D-EXAMPLE-11-16-2002", + "ID", "1-C-EXAMPLE-13-16-2002", "QDATE", "2002-06-01T00:04:00Z", "DOMAIN", "fakesite.example", "EXDATE", "2003-06-01T00:04:00Z")); - assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "1-D-EXAMPLE-11-16-2002")) + assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "1-C-EXAMPLE-13-16-2002")) .atTime("2002-07-01T00:02:00Z") .hasResponse("poll_ack_response_empty.xml"); @@ -775,13 +775,13 @@ class EppLifecycleDomainTest extends EppTestCase { .hasResponse( "poll_response_autorenew.xml", ImmutableMap.of( - "ID", "1-D-EXAMPLE-11-16-2003", // Note -- Year is different from previous ID. + "ID", "1-C-EXAMPLE-13-16-2003", // Note -- Year is different from previous ID. "QDATE", "2003-06-01T00:04:00Z", "DOMAIN", "fakesite.example", "EXDATE", "2004-06-01T00:04:00Z")); // Ack the second poll message and verify that none remain. - assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "1-D-EXAMPLE-11-16-2003")) + assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "1-C-EXAMPLE-13-16-2003")) .atTime("2003-07-01T00:05:05Z") .hasResponse("poll_ack_response_empty.xml"); assertThatCommand("poll.xml") @@ -811,7 +811,7 @@ class EppLifecycleDomainTest extends EppTestCase { // As the losing registrar, read the request poll message, and then ack it. assertThatLoginSucceeds("NewRegistrar", "foo-BAR2"); - String messageId = "1-D-EXAMPLE-19-25-2001"; + String messageId = "1-C-EXAMPLE-19-25-2001"; assertThatCommand("poll.xml") .atTime("2001-01-01T00:01:00Z") .hasResponse("poll_response_domain_transfer_request.xml", ImmutableMap.of("ID", messageId)); @@ -820,7 +820,7 @@ class EppLifecycleDomainTest extends EppTestCase { .hasResponse("poll_ack_response_empty.xml"); // Five days in the future, expect a server approval poll message to the loser, and ack it. - messageId = "1-D-EXAMPLE-19-24-2001"; + messageId = "1-C-EXAMPLE-19-24-2001"; assertThatCommand("poll.xml") .atTime("2001-01-06T00:01:00Z") .hasResponse( @@ -832,7 +832,7 @@ class EppLifecycleDomainTest extends EppTestCase { assertThatLogoutSucceeds(); // Also expect a server approval poll message to the winner, with the transfer request trid. - messageId = "1-D-EXAMPLE-19-23-2001"; + messageId = "1-C-EXAMPLE-19-23-2001"; assertThatLoginSucceeds("TheRegistrar", "password2"); assertThatCommand("poll.xml") .atTime("2001-01-06T00:02:00Z") diff --git a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java index cea34264f..cc44ac6d3 100644 --- a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java +++ b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java @@ -17,6 +17,7 @@ package google.registry.flows; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.EppResourceUtils.loadByForeignKey; +import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; @@ -27,19 +28,28 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.collect.Streams; import com.google.common.flogger.LoggerConfig; import com.google.common.testing.TestLogHandler; import com.googlecode.objectify.Key; import google.registry.flows.FlowUtils.NotLoggedInException; import google.registry.model.EppResource; +import google.registry.model.contact.ContactBase; +import google.registry.model.contact.ContactHistory; +import google.registry.model.domain.DomainContent; +import google.registry.model.domain.DomainHistory; import google.registry.model.eppcommon.Trid; import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; import google.registry.model.eppinput.ResourceCommand; +import google.registry.model.host.HostBase; +import google.registry.model.host.HostHistory; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.EppResourceIndexBucket; +import google.registry.model.reporting.HistoryEntry; import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; +import google.registry.testing.DatabaseHelper; import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.util.TypeUtils.TypeInstantiator; import java.util.logging.Level; @@ -177,4 +187,22 @@ public abstract class ResourceFlowTestCase { private static final ImmutableMap FEE_BASE_MAP = @@ -121,8 +125,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase { try { - HistoryEntry historyEntryDomainCreate = - new HistoryEntry.Builder() + DomainHistory historyEntryDomainCreate = + new DomainHistory.Builder() .setParent(domain) .setType(HistoryEntry.Type.DOMAIN_CREATE) .setModificationTime(clock.nowUtc()) @@ -195,9 +199,10 @@ class DomainRenewFlowTest extends ResourceFlowTestCase { @@ -141,14 +143,14 @@ class DomainRestoreRequestFlowTest clock.advanceOneMilli(); } - @Test + @TestOfyAndSql void testDryRun() throws Exception { setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "example.tld")); persistPendingDeleteDomain(); dryRunFlowAssertResponse(loadFile("generic_success_response.xml")); } - @Test + @TestOfyAndSql void testSuccess_expiryStillInFuture_notExtended() throws Exception { setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "example.tld")); DateTime expirationTime = clock.nowUtc().plusYears(5).plusDays(45); @@ -160,7 +162,8 @@ class DomainRestoreRequestFlowTest DomainBase domain = reloadResourceByForeignKey(); HistoryEntry historyEntryDomainRestore = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_RESTORE); - assertThat(tm().loadByKey(domain.getAutorenewBillingEvent()).getEventTime()) + assertLastHistoryContainsResource(domain); + assertThat(loadByKey(domain.getAutorenewBillingEvent()).getEventTime()) .isEqualTo(expirationTime); assertAboutDomains() .that(domain) @@ -215,7 +218,7 @@ class DomainRestoreRequestFlowTest .build()); } - @Test + @TestOfyAndSql void testSuccess_expiryInPast_extendedByOneYear() throws Exception { setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "example.tld")); DateTime expirationTime = clock.nowUtc().minusDays(20); @@ -228,7 +231,8 @@ class DomainRestoreRequestFlowTest DomainBase domain = reloadResourceByForeignKey(); HistoryEntry historyEntryDomainRestore = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_RESTORE); - assertThat(tm().loadByKey(domain.getAutorenewBillingEvent()).getEventTime()) + assertLastHistoryContainsResource(domain); + assertThat(loadByKey(domain.getAutorenewBillingEvent()).getEventTime()) .isEqualTo(newExpirationTime); assertAboutDomains() .that(domain) @@ -293,7 +297,7 @@ class DomainRestoreRequestFlowTest .build()); } - @Test + @TestOfyAndSql void testSuccess_autorenewEndTimeIsCleared() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP); persistPendingDeleteDomain(); @@ -307,14 +311,14 @@ class DomainRestoreRequestFlowTest assertThat(reloadResourceByForeignKey().getAutorenewEndTime()).isEmpty(); } - @Test + @TestOfyAndSql void testSuccess_fee_v06() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP); persistPendingDeleteDomain(); runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP)); } - @Test + @TestOfyAndSql void testSuccess_fee_v06_noRenewal() throws Exception { setEppInput("domain_update_restore_request_fee_no_renewal.xml", FEE_06_MAP); persistPendingDeleteDomain(clock.nowUtc().plusMonths(6)); @@ -322,42 +326,42 @@ class DomainRestoreRequestFlowTest loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_06_MAP)); } - @Test + @TestOfyAndSql void testSuccess_fee_v11() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_11_MAP); persistPendingDeleteDomain(); runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_11_MAP)); } - @Test + @TestOfyAndSql void testSuccess_fee_v12() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_12_MAP); persistPendingDeleteDomain(); runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_12_MAP)); } - @Test + @TestOfyAndSql void testSuccess_fee_withDefaultAttributes_v06() throws Exception { setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_06_MAP); persistPendingDeleteDomain(); runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP)); } - @Test + @TestOfyAndSql void testSuccess_fee_withDefaultAttributes_v11() throws Exception { setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_11_MAP); persistPendingDeleteDomain(); runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_11_MAP)); } - @Test + @TestOfyAndSql void testSuccess_fee_withDefaultAttributes_v12() throws Exception { setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_12_MAP); persistPendingDeleteDomain(); runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_12_MAP)); } - @Test + @TestOfyAndSql void testFailure_fee_unknownCurrency() { ImmutableMap substitutions = ImmutableMap.of("FEE_VERSION", "0.12", "FEE_NS", "fee12", "CURRENCY", "BAD"); @@ -367,7 +371,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_refundableFee_v06() throws Exception { setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_06_MAP); persistPendingDeleteDomain(); @@ -375,7 +379,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_refundableFee_v11() throws Exception { setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_11_MAP); persistPendingDeleteDomain(); @@ -383,7 +387,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_refundableFee_v12() throws Exception { setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_12_MAP); persistPendingDeleteDomain(); @@ -391,7 +395,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_gracePeriodFee_v06() throws Exception { setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_06_MAP); persistPendingDeleteDomain(); @@ -399,7 +403,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_gracePeriodFee_v11() throws Exception { setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_11_MAP); persistPendingDeleteDomain(); @@ -407,7 +411,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_gracePeriodFee_v12() throws Exception { setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_12_MAP); persistPendingDeleteDomain(); @@ -415,7 +419,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_appliedFee_v06() throws Exception { setEppInput("domain_update_restore_request_fee_applied.xml", FEE_06_MAP); persistPendingDeleteDomain(); @@ -423,7 +427,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_appliedFee_v11() throws Exception { setEppInput("domain_update_restore_request_fee_applied.xml", FEE_11_MAP); persistPendingDeleteDomain(); @@ -431,7 +435,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_appliedFee_v12() throws Exception { setEppInput("domain_update_restore_request_fee_applied.xml", FEE_12_MAP); persistPendingDeleteDomain(); @@ -439,7 +443,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testSuccess_premiumNotBlocked() throws Exception { createTld("example"); setEppInput("domain_update_restore_request_premium.xml"); @@ -447,7 +451,7 @@ class DomainRestoreRequestFlowTest runFlowAssertResponse(loadFile("domain_update_restore_request_response_premium.xml")); } - @Test + @TestOfyAndSql void testSuccess_premiumNotBlocked_andNoRenewal() throws Exception { createTld("example"); setEppInput("domain_update_restore_request_premium_no_renewal.xml"); @@ -456,7 +460,7 @@ class DomainRestoreRequestFlowTest loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_12_MAP)); } - @Test + @TestOfyAndSql void testSuccess_superuserOverridesReservedList() throws Exception { persistResource( Registry.get("tld") @@ -468,7 +472,7 @@ class DomainRestoreRequestFlowTest CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml")); } - @Test + @TestOfyAndSql void testSuccess_superuserOverridesPremiumNameBlock() throws Exception { createTld("example"); setEppInput("domain_update_restore_request_premium.xml"); @@ -481,7 +485,7 @@ class DomainRestoreRequestFlowTest loadFile("domain_update_restore_request_response_premium.xml")); } - @Test + @TestOfyAndSql void testFailure_doesNotExist() throws Exception { ResourceDoesNotExistException thrown = assertThrows(ResourceDoesNotExistException.class, this::runFlow); @@ -489,7 +493,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_suspendedRegistrarCantRestoreDomain() { persistResource( Registrar.loadByClientId("TheRegistrar") @@ -502,7 +506,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_pendingRegistrarCantRestoreDomain() { persistResource( Registrar.loadByClientId("TheRegistrar") @@ -515,7 +519,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_wrongFeeAmount_v06() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP); persistPendingDeleteDomain(); @@ -525,7 +529,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_wrongFeeAmount_v11() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_11_MAP); persistPendingDeleteDomain(); @@ -535,7 +539,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_wrongFeeAmount_v12() throws Exception { setEppInput("domain_update_restore_request_fee.xml", FEE_12_MAP); persistPendingDeleteDomain(); @@ -563,22 +567,22 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_wrongCurrency_v06() throws Exception { runWrongCurrencyTest(FEE_06_MAP); } - @Test + @TestOfyAndSql void testFailure_wrongCurrency_v11() throws Exception { runWrongCurrencyTest(FEE_11_MAP); } - @Test + @TestOfyAndSql void testFailure_wrongCurrency_v12() throws Exception { runWrongCurrencyTest(FEE_12_MAP); } - @Test + @TestOfyAndSql void testFailure_feeGivenInWrongScale_v06() throws Exception { setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_06_MAP); persistPendingDeleteDomain(); @@ -586,7 +590,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_feeGivenInWrongScale_v11() throws Exception { setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_11_MAP); persistPendingDeleteDomain(); @@ -594,7 +598,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_feeGivenInWrongScale_v12() throws Exception { setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_12_MAP); persistPendingDeleteDomain(); @@ -602,7 +606,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_notInRedemptionPeriod() throws Exception { persistResource( newDomainBase(getUniqueIdFromCommand()) @@ -614,21 +618,21 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_notDeleted() throws Exception { persistActiveDomain(getUniqueIdFromCommand()); EppException thrown = assertThrows(DomainNotEligibleForRestoreException.class, this::runFlow); assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_fullyDeleted() throws Exception { persistDeletedDomain(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1)); EppException thrown = assertThrows(ResourceDoesNotExistException.class, this::runFlow); assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_withChange() throws Exception { persistPendingDeleteDomain(); setEppInput("domain_update_restore_request_with_change.xml"); @@ -636,7 +640,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_withAdd() throws Exception { persistPendingDeleteDomain(); setEppInput("domain_update_restore_request_with_add.xml"); @@ -644,7 +648,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_withRemove() throws Exception { persistPendingDeleteDomain(); setEppInput("domain_update_restore_request_with_remove.xml"); @@ -652,7 +656,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_withSecDnsExtension() throws Exception { persistPendingDeleteDomain(); setEppInput("domain_update_restore_request_with_secdns.xml"); @@ -660,7 +664,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_unauthorizedClient() throws Exception { sessionMetadata.setClientId("NewRegistrar"); persistPendingDeleteDomain(); @@ -668,7 +672,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testSuccess_superuserUnauthorizedClient() throws Exception { sessionMetadata.setClientId("NewRegistrar"); persistPendingDeleteDomain(); @@ -679,7 +683,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_notAuthorizedForTld() throws Exception { persistResource( loadRegistrar("TheRegistrar").asBuilder().setAllowedTlds(ImmutableSet.of()).build()); @@ -688,7 +692,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testSuccess_superuserNotAuthorizedForTld() throws Exception { persistResource( loadRegistrar("TheRegistrar").asBuilder().setAllowedTlds(ImmutableSet.of()).build()); @@ -697,7 +701,7 @@ class DomainRestoreRequestFlowTest CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml")); } - @Test + @TestOfyAndSql void testFailure_premiumBlocked() throws Exception { createTld("example"); setEppInput("domain_update_restore_request_premium.xml"); @@ -708,7 +712,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_reservedBlocked() throws Exception { createTld("tld"); persistResource( @@ -721,7 +725,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_premiumNotAcked() throws Exception { createTld("example"); setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "rich.example")); @@ -730,7 +734,7 @@ class DomainRestoreRequestFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testIcannActivityReportField_getsLogged() throws Exception { persistPendingDeleteDomain(); runFlow(); @@ -738,7 +742,7 @@ class DomainRestoreRequestFlowTest assertTldsFieldLogged("tld"); } - @Test + @TestOfyAndSql void testIcannTransactionReportField_getsStored() throws Exception { persistPendingDeleteDomain(); runFlow(); @@ -754,7 +758,7 @@ class DomainRestoreRequestFlowTest 1)); } - @Test + @TestOfyAndSql void testFailure_restoreReportsAreNotSupported() { setEppInput("domain_update_restore_report.xml"); // This exception is referred to by its fully qualified path (rather than being imported) so diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferCancelFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferCancelFlowTest.java index 13f519007..ee0b803c8 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainTransferCancelFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainTransferCancelFlowTest.java @@ -132,6 +132,7 @@ class DomainTransferCancelFlowTest // Transfer should have been cancelled. Verify correct fields were set. domain = reloadResourceByForeignKey(); + assertLastHistoryContainsResource(domain); assertTransferFailed(domain, TransferStatus.CLIENT_CANCELLED, originalTransferData); assertAboutDomains() .that(domain) diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferQueryFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferQueryFlowTest.java index 3cfa26ee5..3901a4276 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainTransferQueryFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainTransferQueryFlowTest.java @@ -34,13 +34,15 @@ import google.registry.model.domain.DomainBase; import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferStatus; +import google.registry.testing.DualDatabaseTest; import google.registry.testing.ReplayExtension; +import google.registry.testing.TestOfyAndSql; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link DomainTransferQueryFlow}. */ +@DualDatabaseTest class DomainTransferQueryFlowTest extends DomainTransferFlowTestCase { @@ -86,67 +88,67 @@ class DomainTransferQueryFlowTest runFlow(); } - @Test + @TestOfyAndSql void testSuccess() throws Exception { doSuccessfulTest("domain_transfer_query.xml", "domain_transfer_query_response.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_sponsoringClient() throws Exception { setClientIdForFlow("TheRegistrar"); doSuccessfulTest("domain_transfer_query.xml", "domain_transfer_query_response.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_domainAuthInfo() throws Exception { setClientIdForFlow("ClientZ"); doSuccessfulTest( "domain_transfer_query_domain_authinfo.xml", "domain_transfer_query_response.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_contactAuthInfo() throws Exception { setClientIdForFlow("ClientZ"); doSuccessfulTest( "domain_transfer_query_contact_authinfo.xml", "domain_transfer_query_response.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_clientApproved() throws Exception { changeTransferStatus(TransferStatus.CLIENT_APPROVED); doSuccessfulTest( "domain_transfer_query.xml", "domain_transfer_query_response_client_approved.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_clientRejected() throws Exception { changeTransferStatus(TransferStatus.CLIENT_REJECTED); doSuccessfulTest( "domain_transfer_query.xml", "domain_transfer_query_response_client_rejected.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_clientCancelled() throws Exception { changeTransferStatus(TransferStatus.CLIENT_CANCELLED); doSuccessfulTest( "domain_transfer_query.xml", "domain_transfer_query_response_client_cancelled.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_serverApproved() throws Exception { changeTransferStatus(TransferStatus.SERVER_APPROVED); doSuccessfulTest( "domain_transfer_query.xml", "domain_transfer_query_response_server_approved.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_serverCancelled() throws Exception { changeTransferStatus(TransferStatus.SERVER_CANCELLED); doSuccessfulTest( "domain_transfer_query.xml", "domain_transfer_query_response_server_cancelled.xml", 1); } - @Test + @TestOfyAndSql void testSuccess_tenYears() throws Exception { // Extend registration by 9 years here; with the extra 1 year from the transfer, we should // hit the 10-year capping. @@ -159,7 +161,7 @@ class DomainTransferQueryFlowTest doSuccessfulTest("domain_transfer_query.xml", "domain_transfer_query_response_10_years.xml", 1); } - @Test + @TestOfyAndSql void testFailure_pendingDeleteDomain() throws Exception { changeTransferStatus(TransferStatus.SERVER_CANCELLED); domain = @@ -168,7 +170,7 @@ class DomainTransferQueryFlowTest "domain_transfer_query.xml", "domain_transfer_query_response_server_cancelled.xml", 1); } - @Test + @TestOfyAndSql void testFailure_badContactPassword() { // Change the contact's password so it does not match the password in the file. contact = @@ -184,7 +186,7 @@ class DomainTransferQueryFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_badDomainPassword() { // Change the domain's password so it does not match the password in the file. domain = @@ -200,7 +202,7 @@ class DomainTransferQueryFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_neverBeenTransferred() { changeTransferStatus(null); EppException thrown = @@ -210,7 +212,7 @@ class DomainTransferQueryFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_unrelatedClient() { setClientIdForFlow("ClientZ"); EppException thrown = @@ -220,7 +222,7 @@ class DomainTransferQueryFlowTest assertAboutEppExceptions().that(thrown).marshalsToXml(); } - @Test + @TestOfyAndSql void testFailure_deletedDomain() throws Exception { domain = persistResource(domain.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build()); @@ -230,7 +232,7 @@ class DomainTransferQueryFlowTest assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand())); } - @Test + @TestOfyAndSql void testFailure_nonexistentDomain() throws Exception { deleteTestDomain(domain, clock.nowUtc()); ResourceDoesNotExistException thrown = @@ -239,14 +241,14 @@ class DomainTransferQueryFlowTest assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand())); } - @Test + @TestOfyAndSql void testIcannActivityReportField_getsLogged() throws Exception { runFlow(); assertIcannReportingActivityFieldLogged("srs-dom-transfer-query"); assertTldsFieldLogged("tld"); } - @Test + @TestOfyAndSql void testSuccess_serverApproved_afterAutorenews() throws Exception { // Set the clock to just past the extended registration time. We'd expect the domain to have // auto-renewed once, but the transfer query response should be the same. diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferRejectFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferRejectFlowTest.java index 263e3fdb0..c896c6db2 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainTransferRejectFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainTransferRejectFlowTest.java @@ -119,6 +119,7 @@ class DomainTransferRejectFlowTest final HistoryEntry historyEntryTransferRejected = getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_REJECT); assertAboutHistoryEntries().that(historyEntryTransferRejected).hasOtherClientId("NewRegistrar"); + assertLastHistoryContainsResource(domain); // The only billing event left should be the original autorenew event, now reopened. assertBillingEvents( getLosingClientAutorenewEvent().asBuilder().setRecurrenceEndTime(END_OF_TIME).build()); diff --git a/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java index d23f08a81..fb24be5b5 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainTransferRequestFlowTest.java @@ -484,6 +484,7 @@ class DomainTransferRequestFlowTest assertAboutDomains() .that(domain) .hasOneHistoryEntryEachOfTypes(DOMAIN_CREATE, DOMAIN_TRANSFER_REQUEST); + assertLastHistoryContainsResource(domain); final HistoryEntry historyEntryTransferRequest = getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_REQUEST); assertAboutHistoryEntries() diff --git a/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java index b3bf5e787..160bf09d4 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainUpdateFlowTest.java @@ -21,11 +21,11 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED; import static google.registry.model.registry.Registry.TldState.QUIET_PERIOD; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.assertBillingEvents; import static google.registry.testing.DatabaseHelper.assertNoBillingEvents; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType; +import static google.registry.testing.DatabaseHelper.loadByKey; import static google.registry.testing.DatabaseHelper.loadRegistrar; import static google.registry.testing.DatabaseHelper.newDomainBase; import static google.registry.testing.DatabaseHelper.persistActiveContact; @@ -90,16 +90,18 @@ import google.registry.model.host.HostResource; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; import google.registry.persistence.VKey; +import google.registry.testing.DualDatabaseTest; import google.registry.testing.ReplayExtension; +import google.registry.testing.TestOfyAndSql; import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link DomainUpdateFlow}. */ +@DualDatabaseTest class DomainUpdateFlowTest extends ResourceFlowTestCase { private static final DelegationSignerData SOME_DSDATA = @@ -207,23 +209,24 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase builder = new ImmutableSet.Builder<>(); for (int i = 0; i < 7; ++i) { @@ -587,7 +590,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase builder = new ImmutableSet.Builder<>(); for (int i = 0; i < 7; ++i) { @@ -644,7 +647,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase builder = new ImmutableSet.Builder<>(); for (int i = 0; i < 8; ++i) { @@ -808,7 +811,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase { - assertThat(tm().loadByKey(contact.getContactKey()).getContactId()).isEqualTo("mak21"); - }); - assertThat(tm().loadByKey(reloadResourceByForeignKey().getRegistrant()).getContactId()) + contact -> + assertThat(loadByKey(contact.getContactKey()).getContactId()).isEqualTo("mak21")); + assertThat(loadByKey(reloadResourceByForeignKey().getRegistrant()).getContactId()) .isEqualTo("mak21"); runFlow(); @@ -1262,15 +1264,13 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase { - assertThat(tm().loadByKey(contact.getContactKey()).getContactId()) - .isEqualTo("sh8013"); - }); - assertThat(tm().loadByKey(reloadResourceByForeignKey().getRegistrant()).getContactId()) + contact -> + assertThat(loadByKey(contact.getContactKey()).getContactId()).isEqualTo("sh8013")); + assertThat(loadByKey(reloadResourceByForeignKey().getRegistrant()).getContactId()) .isEqualTo("sh8013"); } - @Test + @TestOfyAndSql void testSuccess_nameserverAndRegistrantAllowListed() throws Exception { persistReferencedEntities(); persistDomain(); @@ -1283,7 +1283,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase getHistoryEntries(EppResource resource) { - return tm().isOfy() - ? ofy().load().type(HistoryEntry.class).ancestor(resource).order("modificationTime").list() - : tm().transact( - () -> { - ImmutableList unsorted = null; - if (resource instanceof ContactBase) { - unsorted = tm().loadAllOf(ContactHistory.class); - } else if (resource instanceof HostBase) { - unsorted = tm().loadAllOf(HostHistory.class); - } else if (resource instanceof DomainContent) { - unsorted = tm().loadAllOf(DomainHistory.class); - } else { - fail("Expected an EppResource instance, but got " + resource.getClass()); - } - ImmutableList filtered = - unsorted.stream() - .filter( - historyEntry -> - historyEntry.getParent().getName().equals(resource.getRepoId())) - .collect(toImmutableList()); - return ImmutableList.sortedCopyOf( - Comparator.comparing(HistoryEntry::getModificationTime), filtered); - }); + return HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey()); } /** diff --git a/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java b/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java index c76a4100f..84343bbf1 100644 --- a/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java +++ b/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java @@ -108,7 +108,7 @@ class EppLifecycleToolsTest extends EppTestCase { .atTime("2001-06-08T00:00:00Z") .hasResponse("poll_response_unrenew.xml"); - assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "1-9-TLD-20-21-2001")) + assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "1-8-TLD-21-22-2001")) .atTime("2001-06-08T00:00:01Z") .hasResponse("poll_ack_response_empty.xml"); @@ -129,7 +129,7 @@ class EppLifecycleToolsTest extends EppTestCase { .hasResponse( "poll_response_autorenew.xml", ImmutableMap.of( - "ID", "1-9-TLD-20-23-2003", + "ID", "1-8-TLD-21-24-2003", "QDATE", "2003-06-01T00:02:00Z", "DOMAIN", "example.tld", "EXDATE", "2004-06-01T00:02:00Z")); diff --git a/core/src/test/resources/google/registry/flows/poll_response_unrenew.xml b/core/src/test/resources/google/registry/flows/poll_response_unrenew.xml index 13f74e179..e3b486809 100644 --- a/core/src/test/resources/google/registry/flows/poll_response_unrenew.xml +++ b/core/src/test/resources/google/registry/flows/poll_response_unrenew.xml @@ -3,7 +3,7 @@ Command completed successfully; ack to dequeue - + 2001-06-07T00:00:00Z Domain example.tld was unrenewed by 3 years; now expires at 2003-06-01T00:02:00.000Z. diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt index 3d6ddc5f6..8d842e99f 100644 --- a/core/src/test/resources/google/registry/model/schema.txt +++ b/core/src/test/resources/google/registry/model/schema.txt @@ -6,7 +6,7 @@ class google.registry.model.UpdateAutoTimestamp { } class google.registry.model.billing.BillingEvent$Cancellation { @Id java.lang.Long id; - @Parent com.googlecode.objectify.Key parent; + @Parent com.googlecode.objectify.Key parent; google.registry.model.billing.BillingEvent$Reason reason; google.registry.persistence.BillingVKey$BillingEventVKey refOneTime; google.registry.persistence.BillingVKey$BillingRecurrenceVKey refRecurring; @@ -27,7 +27,7 @@ enum google.registry.model.billing.BillingEvent$Flag { } class google.registry.model.billing.BillingEvent$Modification { @Id java.lang.Long id; - @Parent com.googlecode.objectify.Key parent; + @Parent com.googlecode.objectify.Key parent; com.googlecode.objectify.Key eventRef; google.registry.model.billing.BillingEvent$Reason reason; java.lang.String clientId; @@ -39,7 +39,7 @@ class google.registry.model.billing.BillingEvent$Modification { } class google.registry.model.billing.BillingEvent$OneTime { @Id java.lang.Long id; - @Parent com.googlecode.objectify.Key parent; + @Parent com.googlecode.objectify.Key parent; google.registry.model.billing.BillingEvent$Reason reason; google.registry.persistence.VKey cancellationMatchingBillingEvent; google.registry.persistence.VKey allocationToken; @@ -63,7 +63,7 @@ enum google.registry.model.billing.BillingEvent$Reason { } class google.registry.model.billing.BillingEvent$Recurring { @Id java.lang.Long id; - @Parent com.googlecode.objectify.Key parent; + @Parent com.googlecode.objectify.Key parent; google.registry.model.billing.BillingEvent$Reason reason; google.registry.model.common.TimeOfYear recurrenceTimeOfYear; java.lang.String clientId;