google.registry.model.domain.DomainResource.java Source code

Java tutorial

Introduction

Here is the source code for google.registry.model.domain.DomainResource.java

Source

// Copyright 2016 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 static com.google.common.collect.Sets.intersection;
import static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
import static google.registry.model.EppResourceUtils.setAutomaticTransferSuccessProperties;
import static google.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
import static google.registry.util.CollectionUtils.difference;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.CollectionUtils.union;
import static google.registry.util.DateTimeUtils.earliestOf;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Cache;
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.poll.PollMessage;
import google.registry.model.registry.Registry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import java.util.HashSet;
import java.util.Set;
import org.joda.time.DateTime;
import org.joda.time.Interval;

/**
 * A persistable domain resource including mutable and non-mutable fields.
 *
 * @see <a href="https://tools.ietf.org/html/rfc5731">RFC 5731</a>
 */
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
@EntitySubclass(index = true)
@ExternalMessagingName("domain")
public class DomainResource extends DomainBase implements ForeignKeyedEppResource, ResourceWithTransferData {

    /** The max number of years that a domain can be registered for, as set by ICANN policy. */
    public static final int MAX_REGISTRATION_YEARS = 10;

    /** Status values which prohibit DNS information from being published. */
    private static final ImmutableSet<StatusValue> DNS_PUBLISHING_PROHIBITED_STATUSES = ImmutableSet
            .of(StatusValue.CLIENT_HOLD, StatusValue.INACTIVE, StatusValue.PENDING_DELETE, StatusValue.SERVER_HOLD);

    /** Fully qualified host names of this domain's active subordinate hosts. */
    Set<String> subordinateHosts;

    /** When this domain's registration will expire. */
    DateTime registrationExpirationTime;

    /**
     * The poll message associated with this domain being deleted.
     *
     * <p>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.
     */
    Key<PollMessage.OneTime> deletePollMessage;

    /**
     * The recurring billing event associated with this domain's autorenewals.
     *
     * <p>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.
     */
    Key<BillingEvent.Recurring> autorenewBillingEvent;

    /**
     * The recurring poll message associated with this domain's autorenewals.
     *
     * <p>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.
     */
    Key<PollMessage.Autorenew> autorenewPollMessage;

    /** The unexpired grace periods for this domain (some of which may not be active yet). */
    Set<GracePeriod> 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)
    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)
    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)
    Key<DomainApplication> application;

    /** Data about any pending or past transfers on this domain. */
    TransferData transferData;

    /**
     * The time that this resource was last transferred.
     *
     * <p>Can be null if the resource has never been transferred.
     */
    DateTime lastTransferTime;

    public ImmutableSet<String> getSubordinateHosts() {
        return nullToEmptyImmutableCopy(subordinateHosts);
    }

    public DateTime getRegistrationExpirationTime() {
        return registrationExpirationTime;
    }

    public Key<PollMessage.OneTime> getDeletePollMessage() {
        return deletePollMessage;
    }

    public Key<BillingEvent.Recurring> getAutorenewBillingEvent() {
        return autorenewBillingEvent;
    }

    public Key<PollMessage.Autorenew> getAutorenewPollMessage() {
        return autorenewPollMessage;
    }

    public ImmutableSet<GracePeriod> getGracePeriods() {
        return nullToEmptyImmutableCopy(gracePeriods);
    }

    public String getSmdId() {
        return smdId;
    }

    public DateTime getApplicationTime() {
        return applicationTime;
    }

    public Key<DomainApplication> getApplication() {
        return application;
    }

    @Override
    public final TransferData getTransferData() {
        return Optional.fromNullable(transferData).or(TransferData.EMPTY);
    }

    @Override
    public DateTime getLastTransferTime() {
        return lastTransferTime;
    }

    @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.
     *
     * <p>This collects all statuses from the domain's {@link GracePeriod} entries and also adds the
     * PENDING_DELETE status if needed.
     */
    public ImmutableSet<GracePeriodStatus> getGracePeriodStatuses() {
        Set<GracePeriodStatus> 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<DateTime> 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<DomainResource, Builder>
            implements BuilderWithTransferData<Builder> {

        public Builder() {
        }

        private Builder(DomainResource instance) {
            super(instance);
        }

        @Override
        public DomainResource build() {
            // If TransferData is totally empty, set it to null.
            if (TransferData.EMPTY.equals(getInstance().transferData)) {
                setTransferData(null);
            }
            // 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<String> 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<PollMessage.OneTime> deletePollMessage) {
            getInstance().deletePollMessage = deletePollMessage;
            return this;
        }

        public Builder setAutorenewBillingEvent(Key<BillingEvent.Recurring> autorenewBillingEvent) {
            getInstance().autorenewBillingEvent = autorenewBillingEvent;
            return this;
        }

        public Builder setAutorenewPollMessage(Key<PollMessage.Autorenew> 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<DomainApplication> application) {
            getInstance().application = application;
            return this;
        }

        public Builder setGracePeriods(ImmutableSet<GracePeriod> 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;
        }

        @Override
        public Builder setTransferData(TransferData transferData) {
            getInstance().transferData = transferData;
            return thisCastToDerived();
        }

        @Override
        public Builder setLastTransferTime(DateTime lastTransferTime) {
            getInstance().lastTransferTime = lastTransferTime;
            return thisCastToDerived();
        }
    }
}