subordinateHosts;
/** When this domain's registration will expire. */
@XmlElement(name = "exDate")
DateTime registrationExpirationTime;
/**
* The poll message associated with this domain being deleted.
*
* This field should be null if the domain is not in pending delete. If it is, the field should
* refer to a {@link PollMessage} timed to when the domain is fully deleted. If the domain is
* restored, the message should be deleted.
*/
@XmlTransient
Key deletePollMessage;
/**
* The recurring billing event associated with this domain's autorenewals.
*
* The recurrence should be open ended unless the domain is in pending delete or fully deleted,
* in which case it should be closed at the time the delete was requested. Whenever the domain's
* {@link #registrationExpirationTime} is changed the recurrence should be closed, a new one
* should be created, and this field should be updated to point to the new one.
*/
@XmlTransient
Key autorenewBillingEvent;
/**
* The recurring poll message associated with this domain's autorenewals.
*
* The recurrence should be open ended unless the domain is in pending delete or fully deleted,
* in which case it should be closed at the time the delete was requested. Whenever the domain's
* {@link #registrationExpirationTime} is changed the recurrence should be closed, a new one
* should be created, and this field should be updated to point to the new one.
*/
@XmlTransient
Key autorenewPollMessage;
/** The unexpired grace periods for this domain (some of which may not be active yet). */
@XmlTransient
Set gracePeriods;
/**
* The id of the signed mark that was used to create the sunrise application for this domain.
* Will only be populated for domains allocated from a sunrise application.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
String smdId;
/**
* The time that the application used to allocate this domain was created. Will only be populated
* for domains allocated from an application.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
DateTime applicationTime;
/**
* A key to the application used to allocate this domain. Will only be populated for domains
* allocated from an application.
*/
@IgnoreSave(IfNull.class)
@XmlTransient
Key application;
public ImmutableSet getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts);
}
public DateTime getRegistrationExpirationTime() {
return registrationExpirationTime;
}
public Key getDeletePollMessage() {
return deletePollMessage;
}
public Key getAutorenewBillingEvent() {
return autorenewBillingEvent;
}
public Key getAutorenewPollMessage() {
return autorenewPollMessage;
}
public ImmutableSet getGracePeriods() {
return nullToEmptyImmutableCopy(gracePeriods);
}
public String getSmdId() {
return smdId;
}
public DateTime getApplicationTime() {
return applicationTime;
}
public Key getApplication() {
return application;
}
@Override
public String getForeignKey() {
return fullyQualifiedDomainName;
}
/** Returns true if DNS information should be published for the given domain. */
public boolean shouldPublishToDns() {
return intersection(getStatusValues(), DNS_PUBLISHING_PROHIBITED_STATUSES).isEmpty();
}
/**
* Returns the Registry Grace Period Statuses for this domain.
*
* This collects all statuses from the domain's {@link GracePeriod} entries and also adds the
* PENDING_DELETE status if needed.
*/
public ImmutableSet getGracePeriodStatuses() {
Set gracePeriodStatuses = new HashSet<>();
for (GracePeriod gracePeriod : getGracePeriods()) {
gracePeriodStatuses.add(gracePeriod.getType());
}
if (getStatusValues().contains(StatusValue.PENDING_DELETE)
&& !gracePeriodStatuses.contains(GracePeriodStatus.REDEMPTION)) {
gracePeriodStatuses.add(GracePeriodStatus.PENDING_DELETE);
}
return ImmutableSet.copyOf(gracePeriodStatuses);
}
/**
* Returns the Registry Grace Period expiration date for the specified type of grace period for
* this domain, or null if there is no grace period of the specified type.
*/
public Optional getGracePeriodExpirationTime(GracePeriodStatus gracePeriodType) {
for (GracePeriod gracePeriod : getGracePeriods()) {
if (gracePeriod.getType() == gracePeriodType) {
return Optional.of(gracePeriod.getExpirationTime());
}
}
return Optional.absent();
}
/**
* Checks to see if the domain is in a particular type of grace period at the specified time. We
* only check the expiration time, because grace periods are always assumed to start at the
* beginning of time. This could be confusing if asOfDate is in the past. For instance, the Add
* Grace Period will appear to last from the beginning of time until 5 days after the domain is
* created.
*/
public boolean doesAnyGracePeriodOfTypeExpireAfter(
GracePeriodStatus gracePeriodType, DateTime asOfDate) {
for (GracePeriod gracePeriod : getGracePeriods()) {
if ((gracePeriod.getType() == gracePeriodType)
&& gracePeriod.getExpirationTime().isAfter(asOfDate)) {
return true;
}
}
return false;
}
/**
* The logic in this method, which handles implicit server approval of transfers, very closely
* parallels the logic in {@code DomainTransferApproveFlow} which handles explicit client
* approvals.
*/
@Override
public DomainResource cloneProjectedAtTime(final DateTime now) {
TransferData transferData = getTransferData();
DateTime transferExpirationTime = transferData.getPendingTransferExpirationTime();
// If there's a pending transfer that has expired, handle it.
if (TransferStatus.PENDING.equals(transferData.getTransferStatus())
&& isBeforeOrAt(transferExpirationTime, now)) {
// Project until just before the transfer time. This will handle the case of an autorenew
// before the transfer was even requested or during the request period.
// If the transfer time is precisely the moment that the domain expires, there will not be an
// autorenew billing event (since we end the recurrence at transfer time and recurrences are
// exclusive of their ending), and we can just proceed with the transfer.
DomainResource domainAtTransferTime =
cloneProjectedAtTime(transferExpirationTime.minusMillis(1));
// If we are within an autorenew grace period, the transfer will subsume the autorenew. There
// will already be a cancellation written in advance by the transfer request flow, so we don't
// need to worry about billing, but we do need to reduce the number of years added to the
// expiration time by one to account for the year added by the autorenew.
int extraYears = transferData.getExtendedRegistrationYears();
if (domainAtTransferTime.getGracePeriodStatuses().contains(GracePeriodStatus.AUTO_RENEW)) {
extraYears--;
}
// Set the expiration, autorenew events, and grace period for the transfer. (Transfer ends
// all other graces).
Builder builder = domainAtTransferTime.asBuilder()
// Extend the registration by the correct number of years from the expiration time that
// was current on the domain right before the transfer, capped at 10 years from the
// moment of the transfer.
.setRegistrationExpirationTime(extendRegistrationWithCap(
transferExpirationTime,
domainAtTransferTime.getRegistrationExpirationTime(),
extraYears))
// Set the speculatively-written new autorenew events as the domain's autorenew events.
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage())
// Set the grace period using a key to the prescheduled transfer billing event. Not using
// GracePeriod.forBillingEvent() here in order to avoid the actual datastore fetch.
.setGracePeriods(ImmutableSet.of(GracePeriod.create(
GracePeriodStatus.TRANSFER,
transferExpirationTime.plus(Registry.get(getTld()).getTransferGracePeriodLength()),
transferData.getGainingClientId(),
transferData.getServerApproveBillingEvent())));
// Set all remaining transfer properties.
setAutomaticTransferSuccessProperties(builder, transferData);
// Finish projecting to now.
return builder.build().cloneProjectedAtTime(now);
}
// There is no transfer. Do any necessary autorenews.
Builder builder = asBuilder();
if (isBeforeOrAt(registrationExpirationTime, now)) {
// Autorenew by the number of years between the old expiration time and now.
DateTime lastAutorenewTime = leapSafeAddYears(
registrationExpirationTime,
new Interval(registrationExpirationTime, now).toPeriod().getYears());
DateTime newExpirationTime = lastAutorenewTime.plusYears(1);
builder
.setRegistrationExpirationTime(newExpirationTime)
.addGracePeriod(GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
lastAutorenewTime.plus(Registry.get(getTld()).getAutoRenewGracePeriodLength()),
getCurrentSponsorClientId(),
autorenewBillingEvent));
}
// Remove any grace periods that have expired.
DomainResource almostBuilt = builder.build();
builder = almostBuilt.asBuilder();
for (GracePeriod gracePeriod : almostBuilt.getGracePeriods()) {
if (isBeforeOrAt(gracePeriod.getExpirationTime(), now)) {
builder.removeGracePeriod(gracePeriod);
}
}
// Handle common properties like setting or unsetting linked status. This also handles the
// general case of pending transfers for other resource types, but since we've always handled
// a pending transfer by this point that's a no-op for domains.
projectResourceOntoBuilderAtTime(almostBuilt, builder, now);
return builder.build();
}
/** Return what the expiration time would be if the given number of years were added to it. */
public static DateTime extendRegistrationWithCap(
DateTime now, DateTime currentExpirationTime, Integer extendedRegistrationYears) {
// We must cap registration at the max years (aka 10), even if that truncates the last year.
return earliestOf(
leapSafeAddYears(
currentExpirationTime, Optional.fromNullable(extendedRegistrationYears).or(0)),
leapSafeAddYears(now, MAX_REGISTRATION_YEARS));
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link DomainResource}, since it is immutable. */
public static class Builder extends DomainBase.Builder {
public Builder() {}
private Builder(DomainResource instance) {
super(instance);
}
@Override
public DomainResource build() {
// A DomainResource has status INACTIVE if there are no nameservers.
if (getInstance().getNameservers().isEmpty()) {
addStatusValue(StatusValue.INACTIVE);
} else { // There are nameservers, so make sure INACTIVE isn't there.
removeStatusValue(StatusValue.INACTIVE);
}
// This must be called after we add or remove INACTIVE, since that affects whether we get OK.
return super.build();
}
public Builder setSubordinateHosts(ImmutableSet subordinateHosts) {
getInstance().subordinateHosts = subordinateHosts;
return thisCastToDerived();
}
public Builder addSubordinateHost(String hostToAdd) {
return setSubordinateHosts(ImmutableSet.copyOf(
union(getInstance().getSubordinateHosts(), hostToAdd)));
}
public Builder removeSubordinateHost(String hostToRemove) {
return setSubordinateHosts(ImmutableSet.copyOf(
difference(getInstance().getSubordinateHosts(), hostToRemove)));
}
public Builder setRegistrationExpirationTime(DateTime registrationExpirationTime) {
getInstance().registrationExpirationTime = registrationExpirationTime;
return this;
}
public Builder setDeletePollMessage(Key deletePollMessage) {
getInstance().deletePollMessage = deletePollMessage;
return this;
}
public Builder setAutorenewBillingEvent(Key autorenewBillingEvent) {
getInstance().autorenewBillingEvent = autorenewBillingEvent;
return this;
}
public Builder setAutorenewPollMessage(Key autorenewPollMessage) {
getInstance().autorenewPollMessage = autorenewPollMessage;
return this;
}
public Builder setSmdId(String smdId) {
getInstance().smdId = smdId;
return this;
}
public Builder setApplicationTime(DateTime applicationTime) {
getInstance().applicationTime = applicationTime;
return this;
}
public Builder setApplication(Key application) {
getInstance().application = application;
return this;
}
public Builder setGracePeriods(ImmutableSet gracePeriods) {
getInstance().gracePeriods = gracePeriods;
return this;
}
public Builder addGracePeriod(GracePeriod gracePeriod) {
getInstance().gracePeriods = union(getInstance().getGracePeriods(), gracePeriod);
return this;
}
public Builder removeGracePeriod(GracePeriod gracePeriod) {
getInstance().gracePeriods = difference(getInstance().getGracePeriods(), gracePeriod);
return this;
}
}
}