Add SQL schema for GracePeriod (#709)

* Add SQL schema for GracePeriod

* Remove the join table

* Add a domainRepoId in GracePeriod

* Move the clone logic to GracePeriod

* Rebase on HEAD
This commit is contained in:
Shicong Huang
2020-08-06 10:26:19 -04:00
committed by GitHub
parent b2a78b5d68
commit 486bf32353
30 changed files with 518 additions and 135 deletions
@@ -360,7 +360,8 @@ public class DomainCreateFlow implements TransactionalFlow {
command.getNameservers().stream().collect(toImmutableSet()))
.setStatusValues(statuses.build())
.setContacts(command.getContacts())
.addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createBillingEvent))
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
.build();
entitiesToSave.add(
newDomain,
@@ -186,14 +186,18 @@ public final class DomainDeleteFlow implements TransactionalFlow {
DateTime redemptionTime = now.plus(redemptionGracePeriodLength);
asyncTaskEnqueuer.enqueueAsyncResave(
existingDomain, now, ImmutableSortedSet.of(redemptionTime, deletionTime));
builder.setDeletionTime(deletionTime)
builder
.setDeletionTime(deletionTime)
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
// Clear out all old grace periods and add REDEMPTION, which does not include a key to a
// billing event because there isn't one for a domain delete.
.setGracePeriods(ImmutableSet.of(GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
redemptionTime,
clientId)));
.setGracePeriods(
ImmutableSet.of(
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
existingDomain.getRepoId(),
redemptionTime,
clientId)));
// Note: The expiration time is unchanged, so if it's before the new deletion time, there will
// be a "phantom autorenew" where the expiration time advances. No poll message will be
// produced (since we are ending the autorenew recurrences at "now" below) and the billing
@@ -183,7 +183,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, explicitRenewEvent))
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
.build();
EntityChanges entityChanges =
flowCustomLogic.beforeSave(
@@ -192,7 +192,10 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setGracePeriods(
billingEvent.isPresent()
? ImmutableSet.of(
GracePeriod.forBillingEvent(GracePeriodStatus.TRANSFER, billingEvent.get()))
GracePeriod.forBillingEvent(
GracePeriodStatus.TRANSFER,
existingDomain.getRepoId(),
billingEvent.get()))
: ImmutableSet.of())
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(clientId)
@@ -14,6 +14,7 @@
package google.registry.model.domain;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.EppResource;
@@ -27,9 +28,13 @@ import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import org.joda.time.DateTime;
/**
@@ -74,6 +79,17 @@ public class DomainBase extends DomainContent
return super.nsHosts;
}
@Access(AccessType.PROPERTY)
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@JoinColumn(name = "domainRepoId", referencedColumnName = "repoId")
@SuppressWarnings("UnusedMethod")
private Set<GracePeriod> getInternalGracePeriods() {
return gracePeriods;
}
@Override
public VKey<DomainBase> createVKey() {
return VKey.create(DomainBase.class, getRepoId(), Key.create(this));
@@ -73,7 +73,6 @@ import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
@@ -234,7 +233,7 @@ public class DomainContent extends EppResource
VKey<PollMessage.Autorenew> autorenewPollMessage;
/** The unexpired grace periods for this domain (some of which may not be active yet). */
@Transient @ElementCollection Set<GracePeriod> gracePeriods;
@Transient Set<GracePeriod> gracePeriods;
/**
* The id of the signed mark that was used to create this domain in sunrise.
@@ -260,6 +259,18 @@ public class DomainContent extends EppResource
allContacts =
allContacts.stream().map(contact -> contact.reconstitute()).collect(toImmutableSet());
setContactFields(allContacts, true);
// We have to return the cloned object here because the original object's
// hashcode is not correct due to the change to its domainRepoId. The cloned
// object will have a null hashcode so that it can get a recalculated hashcode
// when its hashCode() is invoked.
// TODO(b/162739503): Remove this after fully migrating to Cloud SQL.
if (gracePeriods != null) {
gracePeriods =
gracePeriods.stream()
.map(gracePeriod -> gracePeriod.cloneWithDomainRepoId(getRepoId()))
.collect(toImmutableSet());
}
}
@PostLoad
@@ -354,6 +365,12 @@ public class DomainContent extends EppResource
this.nsHosts = nsHosts;
}
// Hibernate needs this in order to populate gracePeriods but no one else should ever use it
@SuppressWarnings("UnusedMethod")
private void setInternalGracePeriods(Set<GracePeriod> gracePeriods) {
this.gracePeriods = gracePeriods;
}
public final String getCurrentSponsorClientId() {
return getPersistedCurrentSponsorClientId();
}
@@ -448,6 +465,7 @@ public class DomainContent extends EppResource
ImmutableSet.of(
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
transferExpirationTime.plus(
Registry.get(domain.getTld()).getTransferGracePeriodLength()),
transferData.getGainingClientId(),
@@ -483,6 +501,7 @@ public class DomainContent extends EppResource
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
lastAutorenewTime.plus(
Registry.get(domain.getTld()).getAutoRenewGracePeriodLength()),
domain.getCurrentSponsorClientId(),
@@ -18,16 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.persistence.VKey;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;
/**
@@ -37,75 +35,13 @@ import org.joda.time.DateTime;
* the resource is loaded from Datastore.
*/
@Embed
@javax.persistence.Entity
public class GracePeriod extends ImmutableObject {
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Ignore
/** Unique id required for hibernate representation. */
long id;
/** The type of grace period. */
GracePeriodStatus type;
/** When the grace period ends. */
DateTime expirationTime;
/** The registrar to bill. */
@Column(name = "registrarId")
String clientId;
/**
* The one-time billing event corresponding to the action that triggered this grace period, or
* null if not applicable. Not set for autorenew grace periods (which instead use the field {@code
* billingEventRecurring}) or for redemption grace periods (since deletes have no cost).
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
VKey<BillingEvent.OneTime> billingEventOneTime = null;
/**
* The recurring billing event corresponding to the action that triggered this grace period, if
* applicable - i.e. if the action was an autorenew - or null in all other cases.
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
VKey<BillingEvent.Recurring> billingEventRecurring = null;
public GracePeriodStatus getType() {
return type;
}
public DateTime getExpirationTime() {
return expirationTime;
}
public String getClientId() {
return clientId;
}
/** Returns true if this GracePeriod has an associated BillingEvent; i.e. if it's refundable. */
public boolean hasBillingEvent() {
return billingEventOneTime != null || billingEventRecurring != null;
}
/**
* Returns the one time billing event. The value will only be non-null if the type of this grace
* period is not AUTO_RENEW.
*/
public VKey<BillingEvent.OneTime> getOneTimeBillingEvent() {
return billingEventOneTime;
}
/**
* Returns the recurring billing event. The value will only be non-null if the type of this grace
* period is AUTO_RENEW.
*/
public VKey<BillingEvent.Recurring> getRecurringBillingEvent() {
return billingEventRecurring;
}
@Entity
@Table(indexes = @Index(columnList = "domainRepoId"))
public class GracePeriod extends GracePeriodBase {
private static GracePeriod createInternal(
GracePeriodStatus type,
String domainRepoId,
DateTime expirationTime,
String clientId,
@Nullable VKey<BillingEvent.OneTime> billingEventOneTime,
@@ -117,6 +53,7 @@ public class GracePeriod extends ImmutableObject {
"Recurring billing events must be present on (and only on) autorenew grace periods");
GracePeriod instance = new GracePeriod();
instance.type = checkArgumentNotNull(type);
instance.domainRepoId = checkArgumentNotNull(domainRepoId);
instance.expirationTime = checkArgumentNotNull(expirationTime);
instance.clientId = checkArgumentNotNull(clientId);
instance.billingEventOneTime = billingEventOneTime;
@@ -133,32 +70,51 @@ public class GracePeriod extends ImmutableObject {
*/
public static GracePeriod create(
GracePeriodStatus type,
String domainRepoId,
DateTime expirationTime,
String clientId,
@Nullable VKey<BillingEvent.OneTime> billingEventOneTime) {
return createInternal(type, expirationTime, clientId, billingEventOneTime, null);
return createInternal(type, domainRepoId, expirationTime, clientId, billingEventOneTime, null);
}
/** Creates a GracePeriod for a Recurring billing event. */
public static GracePeriod createForRecurring(
GracePeriodStatus type,
String domainRepoId,
DateTime expirationTime,
String clientId,
VKey<Recurring> billingEventRecurring) {
checkArgumentNotNull(billingEventRecurring, "billingEventRecurring cannot be null");
return createInternal(type, expirationTime, clientId, null, billingEventRecurring);
return createInternal(
type, domainRepoId, expirationTime, clientId, null, billingEventRecurring);
}
/** Creates a GracePeriod with no billing event. */
public static GracePeriod createWithoutBillingEvent(
GracePeriodStatus type, DateTime expirationTime, String clientId) {
return createInternal(type, expirationTime, clientId, null, null);
GracePeriodStatus type, String domainRepoId, DateTime expirationTime, String clientId) {
return createInternal(type, domainRepoId, expirationTime, clientId, null, null);
}
/** Constructs a GracePeriod of the given type from the provided one-time BillingEvent. */
public static GracePeriod forBillingEvent(
GracePeriodStatus type, BillingEvent.OneTime billingEvent) {
GracePeriodStatus type, String domainRepoId, BillingEvent.OneTime billingEvent) {
return create(
type, billingEvent.getBillingTime(), billingEvent.getClientId(), billingEvent.createVKey());
type,
domainRepoId,
billingEvent.getBillingTime(),
billingEvent.getClientId(),
billingEvent.createVKey());
}
/**
* Returns a clone of this {@link GracePeriod} with {@link #domainRepoId} set to the given value.
*
* <p>TODO(b/162739503): Remove this function after fully migrating to Cloud SQL.
*/
public GracePeriod cloneWithDomainRepoId(String domainRepoId) {
GracePeriod clone = clone(this);
clone.domainRepoId = checkArgumentNotNull(domainRepoId);
return clone;
}
}
@@ -0,0 +1,114 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.domain;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
/** Base class containing common fields and methods for {@link GracePeriod}. */
@Embed
@MappedSuperclass
public class GracePeriodBase extends ImmutableObject {
/** Unique id required for hibernate representation. */
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Ignore
Long id;
/** Repository id for the domain which this grace period belongs to. */
@Ignore
@Column(nullable = false)
String domainRepoId;
/** The type of grace period. */
@Column(nullable = false)
@Enumerated(EnumType.STRING)
GracePeriodStatus type;
/** When the grace period ends. */
@Column(nullable = false)
DateTime expirationTime;
/** The registrar to bill. */
@Column(name = "registrarId", nullable = false)
String clientId;
/**
* The one-time billing event corresponding to the action that triggered this grace period, or
* null if not applicable. Not set for autorenew grace periods (which instead use the field {@code
* billingEventRecurring}) or for redemption grace periods (since deletes have no cost).
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Column(name = "billing_event_id")
VKey<OneTime> billingEventOneTime = null;
/**
* The recurring billing event corresponding to the action that triggered this grace period, if
* applicable - i.e. if the action was an autorenew - or null in all other cases.
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Column(name = "billing_recurrence_id")
VKey<BillingEvent.Recurring> billingEventRecurring = null;
public GracePeriodStatus getType() {
return type;
}
public String getDomainRepoId() {
return domainRepoId;
}
public DateTime getExpirationTime() {
return expirationTime;
}
public String getClientId() {
return clientId;
}
/** Returns true if this GracePeriod has an associated BillingEvent; i.e. if it's refundable. */
public boolean hasBillingEvent() {
return billingEventOneTime != null || billingEventRecurring != null;
}
/**
* Returns the one time billing event. The value will only be non-null if the type of this grace
* period is not AUTO_RENEW.
*/
public VKey<BillingEvent.OneTime> getOneTimeBillingEvent() {
return billingEventOneTime;
}
/**
* Returns the recurring billing event. The value will only be non-null if the type of this grace
* period is AUTO_RENEW.
*/
public VKey<BillingEvent.Recurring> getRecurringBillingEvent() {
return billingEventRecurring;
}
}