Populate the contact in ContactHistory objects created in Contact flows (#1111)

* Populate the contact in ContactHistory objects created in Contact flows

Minimal interesting changes here
- a bit of reconstruction in ContactHistory to get the repo ID from the
key
- making the History revision ID Long instead of long so that it can be
null in non-built intermediate entities
- adding a copyFrom(HistoryEntry.Builder) method in HistoryEntry.Builder
so that we don't need to allocate quite as many unnecessary IDs, i.e.
removing the .build() lines in provideContactHistory and
provideDomainHistory
This commit is contained in:
gbrodman 2021-05-06 14:38:55 -04:00 committed by GitHub
parent 8034193b88
commit c033720b01
19 changed files with 115 additions and 72 deletions

View file

@ -20,6 +20,7 @@ import com.google.common.base.Strings;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import google.registry.flows.picker.FlowPicker; import google.registry.flows.picker.FlowPicker;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.DomainHistory; import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.AuthInfo;
@ -240,6 +241,12 @@ public class FlowModule {
return historyBuilder; return historyBuilder;
} }
@Provides
static ContactHistory.Builder provideContactHistoryBuilder(
HistoryEntry.Builder historyEntryBuilder) {
return new ContactHistory.Builder().copyFrom(historyEntryBuilder);
}
@Provides @Provides
static DomainHistory.Builder provideDomainHistoryBuilder( static DomainHistory.Builder provideDomainHistoryBuilder(
HistoryEntry.Builder historyEntryBuilder) { HistoryEntry.Builder historyEntryBuilder) {

View file

@ -33,6 +33,7 @@ import google.registry.flows.annotations.ReportingSpec;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException; import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException; import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.contact.ContactCommand.Create; import google.registry.model.contact.ContactCommand.Create;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppinput.ResourceCommand;
@ -61,7 +62,7 @@ public final class ContactCreateFlow implements TransactionalFlow {
@Inject ExtensionManager extensionManager; @Inject ExtensionManager extensionManager;
@Inject @ClientId String clientId; @Inject @ClientId String clientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@Inject HistoryEntry.Builder historyBuilder; @Inject ContactHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject @Config("contactAndHostRoidSuffix") String roidSuffix; @Inject @Config("contactAndHostRoidSuffix") String roidSuffix;
@Inject ContactCreateFlow() {} @Inject ContactCreateFlow() {}
@ -93,12 +94,12 @@ public final class ContactCreateFlow implements TransactionalFlow {
historyBuilder historyBuilder
.setType(HistoryEntry.Type.CONTACT_CREATE) .setType(HistoryEntry.Type.CONTACT_CREATE)
.setModificationTime(now) .setModificationTime(now)
.setXmlBytes(null) // We don't want to store contact details in the history entry. .setXmlBytes(null) // We don't want to store contact details in the history entry.
.setParent(Key.create(newContact)); .setContactBase(newContact);
tm().insertAll( tm().insertAll(
ImmutableSet.of( ImmutableSet.of(
newContact, newContact,
historyBuilder.build().toChildHistoryEntity(), historyBuilder.build(),
ForeignKeyIndex.create(newContact, newContact.getDeletionTime()), ForeignKeyIndex.create(newContact, newContact.getDeletionTime()),
EppResourceIndex.create(Key.create(newContact)))); EppResourceIndex.create(Key.create(newContact))));
return responseBuilder return responseBuilder

View file

@ -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 static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.batch.AsyncTaskEnqueuer; import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager; 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.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow; import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
@ -74,7 +74,7 @@ public final class ContactDeleteFlow implements TransactionalFlow {
@Inject Trid trid; @Inject Trid trid;
@Inject @Superuser boolean isSuperuser; @Inject @Superuser boolean isSuperuser;
@Inject Optional<AuthInfo> authInfo; @Inject Optional<AuthInfo> authInfo;
@Inject HistoryEntry.Builder historyBuilder; @Inject ContactHistory.Builder historyBuilder;
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer; @Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject ContactDeleteFlow() {} @Inject ContactDeleteFlow() {}
@ -99,8 +99,8 @@ public final class ContactDeleteFlow implements TransactionalFlow {
historyBuilder historyBuilder
.setType(HistoryEntry.Type.CONTACT_PENDING_DELETE) .setType(HistoryEntry.Type.CONTACT_PENDING_DELETE)
.setModificationTime(now) .setModificationTime(now)
.setParent(Key.create(existingContact)); .setContactBase(newContact);
tm().insert(historyBuilder.build().toChildHistoryEntity()); tm().insert(historyBuilder.build());
tm().update(newContact); tm().update(newContact);
return responseBuilder.setResultFromCode(SUCCESS_WITH_ACTION_PENDING).build(); return responseBuilder.setResultFromCode(SUCCESS_WITH_ACTION_PENDING).build();
} }

View file

@ -20,19 +20,21 @@ import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException; import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.contact.PostalInfo; import google.registry.model.contact.PostalInfo;
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse; import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse; import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Static utility functions for contact flows. */ /** Static utility functions for contact flows. */
public class ContactFlowUtils { public class ContactFlowUtils {
@ -66,31 +68,35 @@ public class ContactFlowUtils {
/** Create a poll message for the gaining client in a transfer. */ /** Create a poll message for the gaining client in a transfer. */
static PollMessage createGainingTransferPollMessage( static PollMessage createGainingTransferPollMessage(
String targetId, TransferData transferData, HistoryEntry historyEntry) { String targetId,
TransferData transferData,
DateTime now,
Key<ContactHistory> contactHistoryKey) {
return new PollMessage.OneTime.Builder() return new PollMessage.OneTime.Builder()
.setClientId(transferData.getGainingClientId()) .setClientId(transferData.getGainingClientId())
.setEventTime(transferData.getPendingTransferExpirationTime()) .setEventTime(transferData.getPendingTransferExpirationTime())
.setMsg(transferData.getTransferStatus().getMessage()) .setMsg(transferData.getTransferStatus().getMessage())
.setResponseData(ImmutableList.of( .setResponseData(
createTransferResponse(targetId, transferData), ImmutableList.of(
ContactPendingActionNotificationResponse.create( createTransferResponse(targetId, transferData),
targetId, ContactPendingActionNotificationResponse.create(
transferData.getTransferStatus().isApproved(), targetId,
transferData.getTransferRequestTrid(), transferData.getTransferStatus().isApproved(),
historyEntry.getModificationTime()))) transferData.getTransferRequestTrid(),
.setParent(historyEntry) now)))
.setParentKey(contactHistoryKey)
.build(); .build();
} }
/** Create a poll message for the losing client in a transfer. */ /** Create a poll message for the losing client in a transfer. */
static PollMessage createLosingTransferPollMessage( static PollMessage createLosingTransferPollMessage(
String targetId, TransferData transferData, HistoryEntry historyEntry) { String targetId, TransferData transferData, Key<ContactHistory> contactHistoryKey) {
return new PollMessage.OneTime.Builder() return new PollMessage.OneTime.Builder()
.setClientId(transferData.getLosingClientId()) .setClientId(transferData.getLosingClientId())
.setEventTime(transferData.getPendingTransferExpirationTime()) .setEventTime(transferData.getPendingTransferExpirationTime())
.setMsg(transferData.getTransferStatus().getMessage()) .setMsg(transferData.getTransferStatus().getMessage())
.setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData))) .setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData)))
.setParent(historyEntry) .setParentKey(contactHistoryKey)
.build(); .build();
} }

View file

@ -32,6 +32,7 @@ import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.TargetId; import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow; import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.AuthInfo;
@ -66,7 +67,7 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
@Inject @ClientId String clientId; @Inject @ClientId String clientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@Inject Optional<AuthInfo> authInfo; @Inject Optional<AuthInfo> authInfo;
@Inject HistoryEntry.Builder historyBuilder; @Inject ContactHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject ContactTransferApproveFlow() {} @Inject ContactTransferApproveFlow() {}
@ -86,15 +87,17 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
verifyResourceOwnership(clientId, existingContact); verifyResourceOwnership(clientId, existingContact);
ContactResource newContact = ContactResource newContact =
approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now); approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now);
HistoryEntry historyEntry = historyBuilder ContactHistory contactHistory =
.setType(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE) historyBuilder
.setModificationTime(now) .setType(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE)
.setParent(Key.create(existingContact)) .setModificationTime(now)
.build(); .setContactBase(newContact)
.build();
// Create a poll message for the gaining client. // Create a poll message for the gaining client.
PollMessage gainingPollMessage = PollMessage gainingPollMessage =
createGainingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry); createGainingTransferPollMessage(
tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), gainingPollMessage)); targetId, newContact.getTransferData(), now, Key.create(contactHistory));
tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage));
tm().update(newContact); tm().update(newContact);
// Delete the billing event and poll messages that were written in case the transfer would have // Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved. // been implicitly server approved.

View file

@ -32,6 +32,7 @@ import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.TargetId; import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow; import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.AuthInfo;
@ -66,7 +67,7 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
@Inject Optional<AuthInfo> authInfo; @Inject Optional<AuthInfo> authInfo;
@Inject @ClientId String clientId; @Inject @ClientId String clientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@Inject HistoryEntry.Builder historyBuilder; @Inject ContactHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject ContactTransferCancelFlow() {} @Inject ContactTransferCancelFlow() {}
@ -82,15 +83,17 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
verifyTransferInitiator(clientId, existingContact); verifyTransferInitiator(clientId, existingContact);
ContactResource newContact = ContactResource newContact =
denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, clientId); denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, clientId);
HistoryEntry historyEntry = historyBuilder ContactHistory contactHistory =
.setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL) historyBuilder
.setModificationTime(now) .setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL)
.setParent(Key.create(existingContact)) .setModificationTime(now)
.build(); .setContactBase(newContact)
.build();
// Create a poll message for the losing client. // Create a poll message for the losing client.
PollMessage losingPollMessage = PollMessage losingPollMessage =
createLosingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry); createLosingTransferPollMessage(
tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), losingPollMessage)); targetId, newContact.getTransferData(), Key.create(contactHistory));
tm().insertAll(ImmutableSet.of(contactHistory, losingPollMessage));
tm().update(newContact); tm().update(newContact);
// Delete the billing event and poll messages that were written in case the transfer would have // Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved. // been implicitly server approved.

View file

@ -32,6 +32,7 @@ import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.TargetId; import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow; import google.registry.flows.TransactionalFlow;
import google.registry.flows.annotations.ReportingSpec; import google.registry.flows.annotations.ReportingSpec;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.AuthInfo;
@ -64,7 +65,7 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
@Inject Optional<AuthInfo> authInfo; @Inject Optional<AuthInfo> authInfo;
@Inject @ClientId String clientId; @Inject @ClientId String clientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@Inject HistoryEntry.Builder historyBuilder; @Inject ContactHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject ContactTransferRejectFlow() {} @Inject ContactTransferRejectFlow() {}
@ -80,14 +81,16 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
verifyResourceOwnership(clientId, existingContact); verifyResourceOwnership(clientId, existingContact);
ContactResource newContact = ContactResource newContact =
denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, clientId); denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, clientId);
HistoryEntry historyEntry = historyBuilder ContactHistory contactHistory =
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REJECT) historyBuilder
.setModificationTime(now) .setType(HistoryEntry.Type.CONTACT_TRANSFER_REJECT)
.setParent(Key.create(existingContact)) .setModificationTime(now)
.build(); .setContactBase(newContact)
.build();
PollMessage gainingPollMessage = PollMessage gainingPollMessage =
createGainingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry); createGainingTransferPollMessage(
tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), gainingPollMessage)); targetId, newContact.getTransferData(), now, Key.create(contactHistory));
tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage));
tm().update(newContact); tm().update(newContact);
// Delete the billing event and poll messages that were written in case the transfer would have // Delete the billing event and poll messages that were written in case the transfer would have
// been implicitly server approved. // been implicitly server approved.

View file

@ -14,6 +14,7 @@
package google.registry.flows.contact; package google.registry.flows.contact;
import static google.registry.flows.FlowUtils.createHistoryKey;
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence; import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfo; 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.annotations.ReportingSpec;
import google.registry.flows.exceptions.AlreadyPendingTransferException; import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.exceptions.ObjectAlreadySponsoredException; import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo; import google.registry.model.eppcommon.AuthInfo;
@ -81,7 +83,8 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
@Inject @ClientId String gainingClientId; @Inject @ClientId String gainingClientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@Inject @Config("contactAutomaticTransferLength") Duration automaticTransferLength; @Inject @Config("contactAutomaticTransferLength") Duration automaticTransferLength;
@Inject HistoryEntry.Builder historyBuilder;
@Inject ContactHistory.Builder historyBuilder;
@Inject Trid trid; @Inject Trid trid;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject ContactTransferRequestFlow() {} @Inject ContactTransferRequestFlow() {}
@ -105,11 +108,7 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
throw new ObjectAlreadySponsoredException(); throw new ObjectAlreadySponsoredException();
} }
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES); 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); DateTime transferExpirationTime = now.plus(automaticTransferLength);
ContactTransferData serverApproveTransferData = ContactTransferData serverApproveTransferData =
new ContactTransferData.Builder() new ContactTransferData.Builder()
@ -120,12 +119,18 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.setPendingTransferExpirationTime(transferExpirationTime) .setPendingTransferExpirationTime(transferExpirationTime)
.setTransferStatus(TransferStatus.SERVER_APPROVED) .setTransferStatus(TransferStatus.SERVER_APPROVED)
.build(); .build();
Key<ContactHistory> 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. */ // If the transfer is server approved, this message will be sent to the losing registrar. */
PollMessage serverApproveLosingPollMessage = 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. */ // If the transfer is server approved, this message will be sent to the gaining registrar. */
PollMessage serverApproveGainingPollMessage = PollMessage serverApproveGainingPollMessage =
createGainingTransferPollMessage(targetId, serverApproveTransferData, historyEntry); createGainingTransferPollMessage(
targetId, serverApproveTransferData, now, contactHistoryKey);
ContactTransferData pendingTransferData = ContactTransferData pendingTransferData =
serverApproveTransferData serverApproveTransferData
.asBuilder() .asBuilder()
@ -137,8 +142,9 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
.build(); .build();
// When a transfer is requested, a poll message is created to notify the losing registrar. // When a transfer is requested, a poll message is created to notify the losing registrar.
PollMessage requestPollMessage = PollMessage requestPollMessage =
createLosingTransferPollMessage(targetId, pendingTransferData, historyEntry).asBuilder() createLosingTransferPollMessage(targetId, pendingTransferData, contactHistoryKey)
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately. .asBuilder()
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
.build(); .build();
ContactResource newContact = existingContact.asBuilder() ContactResource newContact = existingContact.asBuilder()
.setTransferData(pendingTransferData) .setTransferData(pendingTransferData)
@ -147,7 +153,7 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
tm().update(newContact); tm().update(newContact);
tm().insertAll( tm().insertAll(
ImmutableSet.of( ImmutableSet.of(
historyEntry.toChildHistoryEntity(), historyBuilder.setContactBase(newContact).build(),
requestPollMessage, requestPollMessage,
serverApproveGainingPollMessage, serverApproveGainingPollMessage,
serverApproveLosingPollMessage)); serverApproveLosingPollMessage));

View file

@ -27,7 +27,6 @@ import static google.registry.flows.contact.ContactFlowUtils.validateContactAgai
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.ExtensionManager; import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.ClientId; 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.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.contact.ContactCommand.Update; import google.registry.model.contact.ContactCommand.Update;
import google.registry.model.contact.ContactCommand.Update.Change; 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.ContactResource;
import google.registry.model.contact.PostalInfo; import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.metadata.MetadataExtension; import google.registry.model.domain.metadata.MetadataExtension;
@ -82,7 +82,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
@Inject @ClientId String clientId; @Inject @ClientId String clientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@Inject @Superuser boolean isSuperuser; @Inject @Superuser boolean isSuperuser;
@Inject HistoryEntry.Builder historyBuilder; @Inject ContactHistory.Builder historyBuilder;
@Inject EppResponse.Builder responseBuilder; @Inject EppResponse.Builder responseBuilder;
@Inject ContactUpdateFlow() {} @Inject ContactUpdateFlow() {}
@ -102,11 +102,6 @@ public final class ContactUpdateFlow implements TransactionalFlow {
verifyAllStatusesAreClientSettable(union(statusesToAdd, statusToRemove)); verifyAllStatusesAreClientSettable(union(statusesToAdd, statusToRemove));
} }
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES); 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); checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove);
ContactResource.Builder builder = existingContact.asBuilder(); ContactResource.Builder builder = existingContact.asBuilder();
Change change = command.getInnerChange(); Change change = command.getInnerChange();
@ -150,7 +145,12 @@ public final class ContactUpdateFlow implements TransactionalFlow {
} }
validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo()); validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo());
validateContactAgainstPolicy(newContact); 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); tm().update(newContact);
return responseBuilder.build(); return responseBuilder.build();
} }

View file

@ -287,7 +287,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
} }
@Override @Override
public Builder asBuilder() { public Builder<? extends ContactBase, ?> asBuilder() {
return new Builder<>(clone(this)); return new Builder<>(clone(this));
} }

View file

@ -109,6 +109,9 @@ public class ContactHistory extends HistoryEntry implements SqlEntity {
if (contactBase != null && contactBase.getContactId() == null) { if (contactBase != null && contactBase.getContactId() == null) {
contactBase = 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 // In Datastore, save as a HistoryEntry object regardless of this object's type

View file

@ -193,7 +193,10 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
HistoryEntry historyEntry = Iterables.getLast(DatabaseHelper.getHistoryEntries(resource)); HistoryEntry historyEntry = Iterables.getLast(DatabaseHelper.getHistoryEntries(resource));
if (resource instanceof ContactBase) { if (resource instanceof ContactBase) {
ContactHistory contactHistory = (ContactHistory) historyEntry; ContactHistory contactHistory = (ContactHistory) historyEntry;
assertThat(contactHistory.getContactBase().get()).isEqualTo(resource); // Don't use direct equals comparison since one might be a subclass of the other
assertAboutImmutableObjects()
.that(contactHistory.getContactBase().get())
.isEqualExceptFields(resource);
} else if (resource instanceof DomainContent) { } else if (resource instanceof DomainContent) {
DomainHistory domainHistory = (DomainHistory) historyEntry; DomainHistory domainHistory = (DomainHistory) historyEntry;
assertAboutImmutableObjects() assertAboutImmutableObjects()

View file

@ -56,14 +56,13 @@ class ContactCreateFlowTest extends ResourceFlowTestCase<ContactCreateFlow, Cont
assertTransactionalFlow(true); assertTransactionalFlow(true);
runFlowAssertResponse(loadFile("contact_create_response.xml")); runFlowAssertResponse(loadFile("contact_create_response.xml"));
// Check that the contact was created and persisted with a history entry. // Check that the contact was created and persisted with a history entry.
assertAboutContacts() ContactResource contact = reloadResourceByForeignKey();
.that(reloadResourceByForeignKey()) assertAboutContacts().that(contact).hasOnlyOneHistoryEntryWhich().hasNoXml();
.hasOnlyOneHistoryEntryWhich()
.hasNoXml();
assertNoBillingEvents(); assertNoBillingEvents();
if (tm().isOfy()) { if (tm().isOfy()) {
assertEppResourceIndexEntityFor(reloadResourceByForeignKey()); assertEppResourceIndexEntityFor(contact);
} }
assertLastHistoryContainsResource(contact);
} }
@TestOfyAndSql @TestOfyAndSql

View file

@ -78,6 +78,7 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
.hasOnlyOneHistoryEntryWhich() .hasOnlyOneHistoryEntryWhich()
.hasType(HistoryEntry.Type.CONTACT_PENDING_DELETE); .hasType(HistoryEntry.Type.CONTACT_PENDING_DELETE);
assertNoBillingEvents(); assertNoBillingEvents();
assertLastHistoryContainsResource(deletedContact);
} }
@TestOfyAndSql @TestOfyAndSql

View file

@ -120,6 +120,7 @@ class ContactTransferApproveFlowTest
assertThat(panData.getTrid()) assertThat(panData.getTrid())
.isEqualTo(Trid.create("transferClient-trid", "transferServer-trid")); .isEqualTo(Trid.create("transferClient-trid", "transferServer-trid"));
assertThat(panData.getActionResult()).isTrue(); assertThat(panData.getActionResult()).isTrue();
assertLastHistoryContainsResource(contact);
} }
private void doFailingTest(String commandFilename) throws Exception { private void doFailingTest(String commandFilename) throws Exception {

View file

@ -104,6 +104,7 @@ class ContactTransferCancelFlowTest
.collect(onlyElement()) .collect(onlyElement())
.getTransferStatus()) .getTransferStatus())
.isEqualTo(TransferStatus.CLIENT_CANCELLED); .isEqualTo(TransferStatus.CLIENT_CANCELLED);
assertLastHistoryContainsResource(contact);
} }
private void doFailingTest(String commandFilename) throws Exception { private void doFailingTest(String commandFilename) throws Exception {

View file

@ -119,6 +119,7 @@ class ContactTransferRejectFlowTest
.isEqualTo(Trid.create("transferClient-trid", "transferServer-trid")); .isEqualTo(Trid.create("transferClient-trid", "transferServer-trid"));
assertThat(panData.getActionResult()).isFalse(); assertThat(panData.getActionResult()).isFalse();
assertNoBillingEvents(); assertNoBillingEvents();
assertLastHistoryContainsResource(contact);
} }
private void doFailingTest(String commandFilename) throws Exception { private void doFailingTest(String commandFilename) throws Exception {

View file

@ -137,6 +137,7 @@ class ContactTransferRequestFlowTest
Iterables.filter( Iterables.filter(
loadByKeys(contact.getTransferData().getServerApproveEntities()), PollMessage.class), loadByKeys(contact.getTransferData().getServerApproveEntities()), PollMessage.class),
ImmutableList.of(gainingApproveMessage, losingApproveMessage)); ImmutableList.of(gainingApproveMessage, losingApproveMessage));
assertLastHistoryContainsResource(contact);
} }
private void doFailingTest(String commandFilename) throws Exception { private void doFailingTest(String commandFilename) throws Exception {

View file

@ -64,12 +64,16 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
clock.advanceOneMilli(); clock.advanceOneMilli();
assertTransactionalFlow(true); assertTransactionalFlow(true);
runFlowAssertResponse(loadFile("generic_success_response.xml")); runFlowAssertResponse(loadFile("generic_success_response.xml"));
ContactResource contact = reloadResourceByForeignKey();
// Check that the contact was updated. This value came from the xml. // Check that the contact was updated. This value came from the xml.
assertAboutContacts().that(reloadResourceByForeignKey()) assertAboutContacts()
.hasAuthInfoPwd("2fooBAR").and() .that(contact)
.hasAuthInfoPwd("2fooBAR")
.and()
.hasOnlyOneHistoryEntryWhich() .hasOnlyOneHistoryEntryWhich()
.hasNoXml(); .hasNoXml();
assertNoBillingEvents(); assertNoBillingEvents();
assertLastHistoryContainsResource(contact);
} }
@TestOfyAndSql @TestOfyAndSql