google.registry.flows.domain.DomainDeleteFlow.java Source code

Java tutorial

Introduction

Here is the source code for google.registry.flows.domain.DomainDeleteFlow.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.flows.domain;

import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.handlePendingTransferOnDelete;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.ResourceFlowUtils.updateForeignKeyIndexDeletionTime;
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.util.CollectionUtils.nullToEmpty;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.ResourceFlowUtils;
import google.registry.flows.SessionMetadata;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.custom.DomainDeleteFlowCustomLogic;
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.AfterValidationParameters;
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.BeforeSaveParameters;
import google.registry.flows.custom.EntityChanges;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.DomainResource.Builder;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.fee06.FeeDeleteResponseExtensionV06;
import google.registry.model.domain.fee11.FeeDeleteResponseExtensionV11;
import google.registry.model.domain.fee12.FeeDeleteResponseExtensionV12;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.SecDnsCreateExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.OneTime;
import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferStatus;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.money.Money;
import org.joda.time.DateTime;

/**
 * An EPP flow that deletes a domain.
 *
 * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
 * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
 * @error {@link google.registry.flows.exceptions.OnlyToolCanPassMetadataException}
 * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
 * @error {@link DomainDeleteFlow.DomainToDeleteHasHostsException}
 * @error {@link DomainFlowUtils.BadCommandForRegistryPhaseException}
 * @error {@link DomainFlowUtils.NotAuthorizedForTldException}
 */
public final class DomainDeleteFlow implements TransactionalFlow {

    private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
            StatusValue.CLIENT_DELETE_PROHIBITED, StatusValue.PENDING_DELETE, StatusValue.SERVER_DELETE_PROHIBITED);

    @Inject
    ExtensionManager extensionManager;
    @Inject
    EppInput eppInput;
    @Inject
    SessionMetadata sessionMetadata;
    @Inject
    Optional<AuthInfo> authInfo;
    @Inject
    @ClientId
    String clientId;
    @Inject
    @TargetId
    String targetId;
    @Inject
    @Superuser
    boolean isSuperuser;
    @Inject
    HistoryEntry.Builder historyBuilder;
    @Inject
    DnsQueue dnsQueue;
    @Inject
    Trid trid;
    @Inject
    EppResponse.Builder responseBuilder;
    @Inject
    DomainDeleteFlowCustomLogic customLogic;

    @Inject
    DomainDeleteFlow() {
    }

    @Override
    public final EppResponse run() throws EppException {
        extensionManager.register(MetadataExtension.class, SecDnsCreateExtension.class);
        customLogic.beforeValidation();
        extensionManager.validate();
        validateClientIsLoggedIn(clientId);
        DateTime now = ofy().getTransactionTime();
        // Loads the target resource if it exists
        DomainResource existingDomain = loadAndVerifyExistence(DomainResource.class, targetId, now);
        Registry registry = Registry.get(existingDomain.getTld());
        verifyDeleteAllowed(existingDomain, registry, now);
        customLogic
                .afterValidation(AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build());
        ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
        HistoryEntry historyEntry = buildHistoryEntry(existingDomain, now);
        Builder builder = existingDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
                ? ResourceFlowUtils.<DomainResource, DomainResource.Builder>resolvePendingTransfer(existingDomain,
                        TransferStatus.SERVER_CANCELLED, now)
                : existingDomain.asBuilder();
        builder.setDeletionTime(now).setStatusValues(null);
        // If the domain is in the Add Grace Period, we delete it immediately, which is already
        // reflected in the builder we just prepared. Otherwise we give it a PENDING_DELETE status.
        if (!existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.ADD)) {
            // By default, this should be 30 days of grace, and 5 days of pending delete.
            DateTime deletionTime = now.plus(registry.getRedemptionGracePeriodLength())
                    .plus(registry.getPendingDeleteLength());
            PollMessage.OneTime deletePollMessage = createDeletePollMessage(existingDomain, historyEntry,
                    deletionTime);
            entitiesToSave.add(deletePollMessage);
            builder.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE)).setDeletionTime(deletionTime)
                    // 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,
                                    now.plus(registry.getRedemptionGracePeriodLength()), clientId)))
                    .setDeletePollMessage(Key.create(deletePollMessage));
        }
        DomainResource newDomain = builder.build();
        updateForeignKeyIndexDeletionTime(newDomain);
        handlePendingTransferOnDelete(existingDomain, newDomain, now, historyEntry);
        // Close the autorenew billing event and poll message. This may delete the poll message.
        updateAutorenewRecurrenceEndTime(existingDomain, now);
        // If there's a pending transfer, the gaining client's autorenew billing
        // event and poll message will already have been deleted in
        // ResourceDeleteFlow since it's listed in serverApproveEntities.
        dnsQueue.addDomainRefreshTask(existingDomain.getFullyQualifiedDomainName());
        // Cancel any grace periods that were still active.
        for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
            // No cancellation is written if the grace period was not for a billable event.
            if (gracePeriod.hasBillingEvent()) {
                entitiesToSave.add(BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId));
            }
        }
        entitiesToSave.add(newDomain, historyEntry);
        EntityChanges entityChanges = customLogic.beforeSave(BeforeSaveParameters.newBuilder()
                .setExistingDomain(existingDomain).setNewDomain(newDomain).setHistoryEntry(historyEntry)
                .setEntityChanges(EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build()).build());
        persistEntityChanges(entityChanges);
        BeforeResponseReturnData responseData = customLogic.beforeResponse(BeforeResponseParameters.newBuilder()
                .setResultCode(newDomain.getDeletionTime().isAfter(now) ? SUCCESS_WITH_ACTION_PENDING : SUCCESS)
                .setResponseExtensions(getResponseExtensions(existingDomain, now)).build());
        return responseBuilder.setResultFromCode(responseData.resultCode())
                .setExtensions(responseData.responseExtensions()).build();
    }

    private void verifyDeleteAllowed(DomainResource existingDomain, Registry registry, DateTime now)
            throws EppException {
        verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
        verifyOptionalAuthInfo(authInfo, existingDomain);
        if (!isSuperuser) {
            verifyResourceOwnership(clientId, existingDomain);
            verifyNotInPredelegation(registry, now);
        }
        checkAllowedAccessToTld(clientId, registry.getTld().toString());
        if (!existingDomain.getSubordinateHosts().isEmpty()) {
            throw new DomainToDeleteHasHostsException();
        }
    }

    private HistoryEntry buildHistoryEntry(DomainResource existingResource, DateTime now) {
        return historyBuilder.setType(HistoryEntry.Type.DOMAIN_DELETE).setModificationTime(now)
                .setParent(Key.create(existingResource)).build();
    }

    private OneTime createDeletePollMessage(DomainResource existingResource, HistoryEntry historyEntry,
            DateTime deletionTime) {
        return new PollMessage.OneTime.Builder().setClientId(existingResource.getCurrentSponsorClientId())
                .setEventTime(deletionTime).setMsg("Domain deleted.")
                .setResponseData(ImmutableList.of(DomainPendingActionNotificationResponse
                        .create(existingResource.getFullyQualifiedDomainName(), true, trid, deletionTime)))
                .setParent(historyEntry).build();
    }

    @Nullable
    private ImmutableList<FeeTransformResponseExtension> getResponseExtensions(DomainResource existingDomain,
            DateTime now) {
        FeeTransformResponseExtension.Builder feeResponseBuilder = getDeleteResponseBuilder();
        if (feeResponseBuilder == null) {
            return ImmutableList.of();
        }
        ImmutableList.Builder<Credit> creditsBuilder = new ImmutableList.Builder<>();
        for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
            if (gracePeriod.hasBillingEvent()) {
                Money cost = getGracePeriodCost(gracePeriod, now);
                creditsBuilder.add(Credit.create(cost.negated().getAmount(), FeeType.CREDIT,
                        gracePeriod.getType().getXmlName()));
                feeResponseBuilder.setCurrency(checkNotNull(cost.getCurrencyUnit()));
            }
        }
        ImmutableList<Credit> credits = creditsBuilder.build();
        if (credits.isEmpty()) {
            return ImmutableList.of();
        }
        return ImmutableList.of(feeResponseBuilder.setCredits(credits).build());
    }

    private Money getGracePeriodCost(GracePeriod gracePeriod, DateTime now) {
        if (gracePeriod.getType() == GracePeriodStatus.AUTO_RENEW) {
            DateTime autoRenewTime = ofy().load().key(checkNotNull(gracePeriod.getRecurringBillingEvent())).now()
                    .getRecurrenceTimeOfYear().getLastInstanceBeforeOrAt(now);
            return getDomainRenewCost(targetId, autoRenewTime, 1);
        }
        return ofy().load().key(checkNotNull(gracePeriod.getOneTimeBillingEvent())).now().getCost();
    }

    @Nullable
    private FeeTransformResponseExtension.Builder getDeleteResponseBuilder() {
        Set<String> uris = nullToEmpty(sessionMetadata.getServiceExtensionUris());
        if (uris.contains(ServiceExtension.FEE_0_12.getUri())) {
            return new FeeDeleteResponseExtensionV12.Builder();
        }
        if (uris.contains(ServiceExtension.FEE_0_11.getUri())) {
            return new FeeDeleteResponseExtensionV11.Builder();
        }
        if (uris.contains(ServiceExtension.FEE_0_6.getUri())) {
            return new FeeDeleteResponseExtensionV06.Builder();
        }
        return null;
    }

    /** Domain to be deleted has subordinate hosts. */
    static class DomainToDeleteHasHostsException extends AssociationProhibitsOperationException {
        public DomainToDeleteHasHostsException() {
            super("Domain to be deleted has subordinate hosts");
        }
    }
}