diff --git a/core/src/main/java/google/registry/flows/FlowModule.java b/core/src/main/java/google/registry/flows/FlowModule.java index 94c458e70..2a5faf32b 100644 --- a/core/src/main/java/google/registry/flows/FlowModule.java +++ b/core/src/main/java/google/registry/flows/FlowModule.java @@ -20,6 +20,7 @@ import com.google.common.base.Strings; import dagger.Module; import dagger.Provides; import google.registry.flows.picker.FlowPicker; +import google.registry.model.contact.ContactHistory; import google.registry.model.domain.DomainHistory; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; @@ -240,6 +241,12 @@ public class FlowModule { return historyBuilder; } + @Provides + static ContactHistory.Builder provideContactHistoryBuilder( + HistoryEntry.Builder historyEntryBuilder) { + return new ContactHistory.Builder().copyFrom(historyEntryBuilder); + } + @Provides static DomainHistory.Builder provideDomainHistoryBuilder( HistoryEntry.Builder historyEntryBuilder) { diff --git a/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java b/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java index cda40affa..0bb731396 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java @@ -33,6 +33,7 @@ import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException; import google.registry.flows.exceptions.ResourceCreateContentionException; import google.registry.model.contact.ContactCommand.Create; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppinput.ResourceCommand; @@ -61,7 +62,7 @@ public final class ContactCreateFlow implements TransactionalFlow { @Inject ExtensionManager extensionManager; @Inject @ClientId String clientId; @Inject @TargetId String targetId; - @Inject HistoryEntry.Builder historyBuilder; + @Inject ContactHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject @Config("contactAndHostRoidSuffix") String roidSuffix; @Inject ContactCreateFlow() {} @@ -93,12 +94,12 @@ public final class ContactCreateFlow implements TransactionalFlow { historyBuilder .setType(HistoryEntry.Type.CONTACT_CREATE) .setModificationTime(now) - .setXmlBytes(null) // We don't want to store contact details in the history entry. - .setParent(Key.create(newContact)); + .setXmlBytes(null) // We don't want to store contact details in the history entry. + .setContactBase(newContact); tm().insertAll( ImmutableSet.of( newContact, - historyBuilder.build().toChildHistoryEntity(), + historyBuilder.build(), ForeignKeyIndex.create(newContact, newContact.getDeletionTime()), EppResourceIndex.create(Key.create(newContact)))); return responseBuilder diff --git a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java index bc686f185..21ceb5345 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java @@ -24,7 +24,6 @@ import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PE import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import com.google.common.collect.ImmutableSet; -import com.googlecode.objectify.Key; import google.registry.batch.AsyncTaskEnqueuer; import google.registry.flows.EppException; import google.registry.flows.ExtensionManager; @@ -33,6 +32,7 @@ import google.registry.flows.FlowModule.Superuser; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainBase; import google.registry.model.domain.metadata.MetadataExtension; @@ -74,7 +74,7 @@ public final class ContactDeleteFlow implements TransactionalFlow { @Inject Trid trid; @Inject @Superuser boolean isSuperuser; @Inject Optional authInfo; - @Inject HistoryEntry.Builder historyBuilder; + @Inject ContactHistory.Builder historyBuilder; @Inject AsyncTaskEnqueuer asyncTaskEnqueuer; @Inject EppResponse.Builder responseBuilder; @Inject ContactDeleteFlow() {} @@ -99,8 +99,8 @@ public final class ContactDeleteFlow implements TransactionalFlow { historyBuilder .setType(HistoryEntry.Type.CONTACT_PENDING_DELETE) .setModificationTime(now) - .setParent(Key.create(existingContact)); - tm().insert(historyBuilder.build().toChildHistoryEntity()); + .setContactBase(newContact); + tm().insert(historyBuilder.build()); tm().update(newContact); return responseBuilder.setResultFromCode(SUCCESS_WITH_ACTION_PENDING).build(); } diff --git a/core/src/main/java/google/registry/flows/contact/ContactFlowUtils.java b/core/src/main/java/google/registry/flows/contact/ContactFlowUtils.java index 753a64b09..d039670fb 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactFlowUtils.java +++ b/core/src/main/java/google/registry/flows/contact/ContactFlowUtils.java @@ -20,19 +20,21 @@ import com.google.common.base.CharMatcher; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; +import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.EppException.ParameterValuePolicyErrorException; import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.model.contact.ContactAddress; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.contact.PostalInfo; import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse; import google.registry.model.poll.PollMessage; -import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferResponse.ContactTransferResponse; import java.util.Set; import javax.annotation.Nullable; +import org.joda.time.DateTime; /** Static utility functions for contact flows. */ public class ContactFlowUtils { @@ -66,31 +68,35 @@ public class ContactFlowUtils { /** Create a poll message for the gaining client in a transfer. */ static PollMessage createGainingTransferPollMessage( - String targetId, TransferData transferData, HistoryEntry historyEntry) { + String targetId, + TransferData transferData, + DateTime now, + Key contactHistoryKey) { return new PollMessage.OneTime.Builder() .setClientId(transferData.getGainingClientId()) .setEventTime(transferData.getPendingTransferExpirationTime()) .setMsg(transferData.getTransferStatus().getMessage()) - .setResponseData(ImmutableList.of( - createTransferResponse(targetId, transferData), - ContactPendingActionNotificationResponse.create( - targetId, - transferData.getTransferStatus().isApproved(), - transferData.getTransferRequestTrid(), - historyEntry.getModificationTime()))) - .setParent(historyEntry) + .setResponseData( + ImmutableList.of( + createTransferResponse(targetId, transferData), + ContactPendingActionNotificationResponse.create( + targetId, + transferData.getTransferStatus().isApproved(), + transferData.getTransferRequestTrid(), + now))) + .setParentKey(contactHistoryKey) .build(); } /** Create a poll message for the losing client in a transfer. */ static PollMessage createLosingTransferPollMessage( - String targetId, TransferData transferData, HistoryEntry historyEntry) { + String targetId, TransferData transferData, Key contactHistoryKey) { return new PollMessage.OneTime.Builder() .setClientId(transferData.getLosingClientId()) .setEventTime(transferData.getPendingTransferExpirationTime()) .setMsg(transferData.getTransferStatus().getMessage()) .setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData))) - .setParent(historyEntry) + .setParentKey(contactHistoryKey) .build(); } diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java index affdf9836..acf05f612 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java @@ -32,6 +32,7 @@ import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; @@ -66,7 +67,7 @@ public final class ContactTransferApproveFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject Optional authInfo; - @Inject HistoryEntry.Builder historyBuilder; + @Inject ContactHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject ContactTransferApproveFlow() {} @@ -86,15 +87,17 @@ public final class ContactTransferApproveFlow implements TransactionalFlow { verifyResourceOwnership(clientId, existingContact); ContactResource newContact = approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now); - HistoryEntry historyEntry = historyBuilder - .setType(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE) - .setModificationTime(now) - .setParent(Key.create(existingContact)) - .build(); + ContactHistory contactHistory = + historyBuilder + .setType(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE) + .setModificationTime(now) + .setContactBase(newContact) + .build(); // Create a poll message for the gaining client. PollMessage gainingPollMessage = - createGainingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry); - tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), gainingPollMessage)); + createGainingTransferPollMessage( + targetId, newContact.getTransferData(), now, Key.create(contactHistory)); + tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage)); tm().update(newContact); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java index 7824f936d..46b2f59e7 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java @@ -32,6 +32,7 @@ import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; @@ -66,7 +67,7 @@ public final class ContactTransferCancelFlow implements TransactionalFlow { @Inject Optional authInfo; @Inject @ClientId String clientId; @Inject @TargetId String targetId; - @Inject HistoryEntry.Builder historyBuilder; + @Inject ContactHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject ContactTransferCancelFlow() {} @@ -82,15 +83,17 @@ public final class ContactTransferCancelFlow implements TransactionalFlow { verifyTransferInitiator(clientId, existingContact); ContactResource newContact = denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, clientId); - HistoryEntry historyEntry = historyBuilder - .setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL) - .setModificationTime(now) - .setParent(Key.create(existingContact)) - .build(); + ContactHistory contactHistory = + historyBuilder + .setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL) + .setModificationTime(now) + .setContactBase(newContact) + .build(); // Create a poll message for the losing client. PollMessage losingPollMessage = - createLosingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry); - tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), losingPollMessage)); + createLosingTransferPollMessage( + targetId, newContact.getTransferData(), Key.create(contactHistory)); + tm().insertAll(ImmutableSet.of(contactHistory, losingPollMessage)); tm().update(newContact); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java index 52d5b288d..1c9003c08 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java @@ -32,6 +32,7 @@ import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; @@ -64,7 +65,7 @@ public final class ContactTransferRejectFlow implements TransactionalFlow { @Inject Optional authInfo; @Inject @ClientId String clientId; @Inject @TargetId String targetId; - @Inject HistoryEntry.Builder historyBuilder; + @Inject ContactHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject ContactTransferRejectFlow() {} @@ -80,14 +81,16 @@ public final class ContactTransferRejectFlow implements TransactionalFlow { verifyResourceOwnership(clientId, existingContact); ContactResource newContact = denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, clientId); - HistoryEntry historyEntry = historyBuilder - .setType(HistoryEntry.Type.CONTACT_TRANSFER_REJECT) - .setModificationTime(now) - .setParent(Key.create(existingContact)) - .build(); + ContactHistory contactHistory = + historyBuilder + .setType(HistoryEntry.Type.CONTACT_TRANSFER_REJECT) + .setModificationTime(now) + .setContactBase(newContact) + .build(); PollMessage gainingPollMessage = - createGainingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry); - tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), gainingPollMessage)); + createGainingTransferPollMessage( + targetId, newContact.getTransferData(), now, Key.create(contactHistory)); + tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage)); tm().update(newContact); // Delete the billing event and poll messages that were written in case the transfer would have // been implicitly server approved. diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java index bcf4b59db..1b115d402 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.contact; +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.verifyAuthInfo; @@ -36,6 +37,7 @@ import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.exceptions.AlreadyPendingTransferException; import google.registry.flows.exceptions.ObjectAlreadySponsoredException; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.eppcommon.AuthInfo; @@ -81,7 +83,8 @@ public final class ContactTransferRequestFlow implements TransactionalFlow { @Inject @ClientId String gainingClientId; @Inject @TargetId String targetId; @Inject @Config("contactAutomaticTransferLength") Duration automaticTransferLength; - @Inject HistoryEntry.Builder historyBuilder; + + @Inject ContactHistory.Builder historyBuilder; @Inject Trid trid; @Inject EppResponse.Builder responseBuilder; @Inject ContactTransferRequestFlow() {} @@ -105,11 +108,7 @@ public final class ContactTransferRequestFlow implements TransactionalFlow { throw new ObjectAlreadySponsoredException(); } verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES); - HistoryEntry historyEntry = historyBuilder - .setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST) - .setModificationTime(now) - .setParent(Key.create(existingContact)) - .build(); + DateTime transferExpirationTime = now.plus(automaticTransferLength); ContactTransferData serverApproveTransferData = new ContactTransferData.Builder() @@ -120,12 +119,18 @@ public final class ContactTransferRequestFlow implements TransactionalFlow { .setPendingTransferExpirationTime(transferExpirationTime) .setTransferStatus(TransferStatus.SERVER_APPROVED) .build(); + Key contactHistoryKey = createHistoryKey(existingContact, ContactHistory.class); + historyBuilder + .setId(contactHistoryKey.getId()) + .setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST) + .setModificationTime(now); // If the transfer is server approved, this message will be sent to the losing registrar. */ PollMessage serverApproveLosingPollMessage = - createLosingTransferPollMessage(targetId, serverApproveTransferData, historyEntry); + createLosingTransferPollMessage(targetId, serverApproveTransferData, contactHistoryKey); // If the transfer is server approved, this message will be sent to the gaining registrar. */ PollMessage serverApproveGainingPollMessage = - createGainingTransferPollMessage(targetId, serverApproveTransferData, historyEntry); + createGainingTransferPollMessage( + targetId, serverApproveTransferData, now, contactHistoryKey); ContactTransferData pendingTransferData = serverApproveTransferData .asBuilder() @@ -137,8 +142,9 @@ public final class ContactTransferRequestFlow implements TransactionalFlow { .build(); // When a transfer is requested, a poll message is created to notify the losing registrar. PollMessage requestPollMessage = - createLosingTransferPollMessage(targetId, pendingTransferData, historyEntry).asBuilder() - .setEventTime(now) // Unlike the serverApprove messages, this applies immediately. + createLosingTransferPollMessage(targetId, pendingTransferData, contactHistoryKey) + .asBuilder() + .setEventTime(now) // Unlike the serverApprove messages, this applies immediately. .build(); ContactResource newContact = existingContact.asBuilder() .setTransferData(pendingTransferData) @@ -147,7 +153,7 @@ public final class ContactTransferRequestFlow implements TransactionalFlow { tm().update(newContact); tm().insertAll( ImmutableSet.of( - historyEntry.toChildHistoryEntity(), + historyBuilder.setContactBase(newContact).build(), requestPollMessage, serverApproveGainingPollMessage, serverApproveLosingPollMessage)); diff --git a/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java b/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java index ba8080744..120aed7b1 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java @@ -27,7 +27,6 @@ import static google.registry.flows.contact.ContactFlowUtils.validateContactAgai import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import com.google.common.collect.ImmutableSet; -import com.googlecode.objectify.Key; import google.registry.flows.EppException; import google.registry.flows.ExtensionManager; import google.registry.flows.FlowModule.ClientId; @@ -38,6 +37,7 @@ import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException; import google.registry.model.contact.ContactCommand.Update; import google.registry.model.contact.ContactCommand.Update.Change; +import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; import google.registry.model.contact.PostalInfo; import google.registry.model.domain.metadata.MetadataExtension; @@ -82,7 +82,7 @@ public final class ContactUpdateFlow implements TransactionalFlow { @Inject @ClientId String clientId; @Inject @TargetId String targetId; @Inject @Superuser boolean isSuperuser; - @Inject HistoryEntry.Builder historyBuilder; + @Inject ContactHistory.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; @Inject ContactUpdateFlow() {} @@ -102,11 +102,6 @@ public final class ContactUpdateFlow implements TransactionalFlow { verifyAllStatusesAreClientSettable(union(statusesToAdd, statusToRemove)); } verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES); - historyBuilder - .setType(HistoryEntry.Type.CONTACT_UPDATE) - .setModificationTime(now) - .setXmlBytes(null) // We don't want to store contact details in the history entry. - .setParent(Key.create(existingContact)); checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove); ContactResource.Builder builder = existingContact.asBuilder(); Change change = command.getInnerChange(); @@ -150,7 +145,12 @@ public final class ContactUpdateFlow implements TransactionalFlow { } validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo()); validateContactAgainstPolicy(newContact); - tm().insert(historyBuilder.build().toChildHistoryEntity()); + historyBuilder + .setType(HistoryEntry.Type.CONTACT_UPDATE) + .setModificationTime(now) + .setXmlBytes(null) // We don't want to store contact details in the history entry. + .setContactBase(newContact); + tm().insert(historyBuilder.build()); tm().update(newContact); return responseBuilder.build(); } diff --git a/core/src/main/java/google/registry/model/contact/ContactBase.java b/core/src/main/java/google/registry/model/contact/ContactBase.java index 8212f0124..47b884b7f 100644 --- a/core/src/main/java/google/registry/model/contact/ContactBase.java +++ b/core/src/main/java/google/registry/model/contact/ContactBase.java @@ -287,7 +287,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData } @Override - public Builder asBuilder() { + public Builder asBuilder() { return new Builder<>(clone(this)); } diff --git a/core/src/main/java/google/registry/model/contact/ContactHistory.java b/core/src/main/java/google/registry/model/contact/ContactHistory.java index c93d63991..c266244f3 100644 --- a/core/src/main/java/google/registry/model/contact/ContactHistory.java +++ b/core/src/main/java/google/registry/model/contact/ContactHistory.java @@ -109,6 +109,9 @@ public class ContactHistory extends HistoryEntry implements SqlEntity { if (contactBase != null && contactBase.getContactId() == null) { contactBase = null; } + if (contactBase != null && contactBase.getRepoId() == null) { + contactBase = contactBase.asBuilder().setRepoId(parent.getName()).build(); + } } // In Datastore, save as a HistoryEntry object regardless of this object's type diff --git a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java index cc44ac6d3..e0a1058f9 100644 --- a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java +++ b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java @@ -193,7 +193,10 @@ public abstract class ResourceFlowTestCase