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

Java tutorial

Introduction

Here is the source code for google.registry.flows.domain.DomainCreateFlow.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 google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences;
import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse;
import static google.registry.flows.domain.DomainFlowUtils.failfastForCreate;
import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity;
import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables;
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
import static google.registry.flows.domain.DomainFlowUtils.validateLaunchCreateNotice;
import static google.registry.flows.domain.DomainFlowUtils.validateSecDnsExtension;
import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsNoticeIfAndOnlyIfNeeded;
import static google.registry.flows.domain.DomainFlowUtils.verifyClaimsPeriodNotEnded;
import static google.registry.flows.domain.DomainFlowUtils.verifyLaunchPhaseMatchesRegistryPhase;
import static google.registry.flows.domain.DomainFlowUtils.verifyNoCodeMarks;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
import static google.registry.model.EppResourceUtils.createDomainRepoId;
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
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.TransactionalFlow;
import google.registry.flows.custom.DomainCreateFlowCustomLogic;
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseParameters;
import google.registry.flows.custom.DomainCreateFlowCustomLogic.BeforeResponseReturnData;
import google.registry.flows.custom.EntityChanges;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.DomainApplication;
import google.registry.model.domain.DomainCommand.Create;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.launch.LaunchCreateExtension;
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.eppinput.EppInput;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.DomainCreateData;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.ofy.ObjectifyService;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.Autorenew;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.reporting.HistoryEntry;
import google.registry.tmch.LordnTask;
import java.util.Set;
import javax.inject.Inject;
import org.joda.time.DateTime;

/**
 * An EPP flow that creates a new domain resource.
 *
 * @error {@link google.registry.flows.exceptions.OnlyToolCanPassMetadataException}
 * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException}
 * @error {@link google.registry.flows.EppException.UnimplementedExtensionException}
 * @error {@link google.registry.flows.ExtensionManager.UndeclaredServiceExtensionException}
 * @error {@link google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
 * @error {@link DomainFlowUtils.AcceptedTooLongAgoException}
 * @error {@link DomainFlowUtils.BadDomainNameCharacterException}
 * @error {@link DomainFlowUtils.BadDomainNamePartsCountException}
 * @error {@link DomainFlowUtils.BadPeriodUnitException}
 * @error {@link DomainFlowUtils.ClaimsPeriodEndedException}
 * @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
 * @error {@link DomainFlowUtils.CurrencyValueScaleException}
 * @error {@link DomainFlowUtils.DashesInThirdAndFourthException}
 * @error {@link DomainFlowUtils.DomainLabelTooLongException}
 * @error {@link DomainFlowUtils.DomainReservedException}
 * @error {@link DomainFlowUtils.DuplicateContactForRoleException}
 * @error {@link DomainFlowUtils.EmptyDomainNamePartException}
 * @error {@link DomainFlowUtils.ExpiredClaimException}
 * @error {@link DomainFlowUtils.FeesMismatchException}
 * @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
 * @error {@link DomainFlowUtils.InvalidIdnDomainLabelException}
 * @error {@link DomainFlowUtils.InvalidLrpTokenException}
 * @error {@link DomainFlowUtils.InvalidPunycodeException}
 * @error {@link DomainFlowUtils.InvalidTcnIdChecksumException}
 * @error {@link DomainFlowUtils.InvalidTrademarkValidatorException}
 * @error {@link DomainFlowUtils.LeadingDashException}
 * @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException}
 * @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
 * @error {@link DomainFlowUtils.MalformedTcnIdException}
 * @error {@link DomainFlowUtils.MaxSigLifeNotSupportedException}
 * @error {@link DomainFlowUtils.MissingAdminContactException}
 * @error {@link DomainFlowUtils.MissingClaimsNoticeException}
 * @error {@link DomainFlowUtils.MissingContactTypeException}
 * @error {@link DomainFlowUtils.MissingRegistrantException}
 * @error {@link DomainFlowUtils.MissingTechnicalContactException}
 * @error {@link DomainFlowUtils.NameserversNotAllowedException}
 * @error {@link DomainFlowUtils.NameserversNotSpecifiedException}
 * @error {@link DomainFlowUtils.PremiumNameBlockedException}
 * @error {@link DomainFlowUtils.RegistrantNotAllowedException}
 * @error {@link DomainFlowUtils.TldDoesNotExistException}
 * @error {@link DomainFlowUtils.TooManyDsRecordsException}
 * @error {@link DomainFlowUtils.TooManyNameserversException}
 * @error {@link DomainFlowUtils.TrailingDashException}
 * @error {@link DomainFlowUtils.UnexpectedClaimsNoticeException}
 * @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
 * @error {@link DomainFlowUtils.UnsupportedMarkTypeException}
 * @error {@link DomainCreateFlow.DomainHasOpenApplicationsException}
 * @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
 */

public class DomainCreateFlow implements TransactionalFlow {

    private static final Set<TldState> SUNRISE_STATES = Sets.immutableEnumSet(TldState.SUNRISE, TldState.SUNRUSH);

    @Inject
    ExtensionManager extensionManager;
    @Inject
    EppInput eppInput;
    @Inject
    AuthInfo authInfo;
    @Inject
    ResourceCommand resourceCommand;
    @Inject
    @ClientId
    String clientId;
    @Inject
    @TargetId
    String targetId;
    @Inject
    @Superuser
    boolean isSuperuser;
    @Inject
    HistoryEntry.Builder historyBuilder;
    @Inject
    EppResponse.Builder responseBuilder;
    @Inject
    DomainCreateFlowCustomLogic customLogic;
    @Inject
    DomainFlowTmchUtils tmchUtils;
    @Inject
    DomainPricingLogic pricingLogic;
    @Inject
    DnsQueue dnsQueue;

    @Inject
    DomainCreateFlow() {
    }

    @Override
    public final EppResponse run() throws EppException {
        extensionManager.register(FeeCreateCommandExtension.class, SecDnsCreateExtension.class,
                MetadataExtension.class, LaunchCreateExtension.class);
        customLogic.beforeValidation();
        extensionManager.validate();
        validateClientIsLoggedIn(clientId);
        DateTime now = ofy().getTransactionTime();
        Create command = cloneAndLinkReferences((Create) resourceCommand, now);
        Period period = command.getPeriod();
        verifyUnitIsYears(period);
        int years = period.getValue();
        failfastForCreate(targetId, now);
        verifyResourceDoesNotExist(DomainResource.class, targetId, now);
        // Validate that this is actually a legal domain name on a TLD that the registrar has access to.
        InternetDomainName domainName = validateDomainName(command.getFullyQualifiedDomainName());
        String domainLabel = domainName.parts().get(0);
        Registry registry = Registry.get(domainName.parent().toString());
        validateCreateCommandContactsAndNameservers(command, registry.getTldStr());
        TldState tldState = registry.getTldState(now);
        boolean isAnchorTenant = isAnchorTenant(domainName);
        LaunchCreateExtension launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class);
        boolean hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty();
        boolean hasClaimsNotice = launchCreate != null && launchCreate.getNotice() != null;
        if (launchCreate != null) {
            verifyNoCodeMarks(launchCreate);
            validateLaunchCreateNotice(launchCreate.getNotice(), domainLabel, isSuperuser, now);
        }
        boolean isSunriseCreate = hasSignedMarks && SUNRISE_STATES.contains(tldState);
        // Superusers can create reserved domains, force creations on domains that require a claims
        // notice without specifying a claims key, ignore the registry phase, and override blocks on
        // registering premium domains.
        if (!isSuperuser) {
            checkAllowedAccessToTld(clientId, registry.getTldStr());
            if (launchCreate != null) {
                verifyLaunchPhaseMatchesRegistryPhase(registry, launchCreate, now);
            }
            if (!isAnchorTenant) {
                verifyNotReserved(domainName, isSunriseCreate);
            }
            if (hasClaimsNotice) {
                verifyClaimsPeriodNotEnded(registry, now);
            }
            if (now.isBefore(registry.getClaimsPeriodEnd())) {
                verifyClaimsNoticeIfAndOnlyIfNeeded(domainName, hasSignedMarks, hasClaimsNotice);
            }
            verifyPremiumNameIsNotBlocked(targetId, now, clientId);
            verifyNoOpenApplications(now);
            verifyIsGaOrIsSpecialCase(tldState, isAnchorTenant);
        }
        String signedMarkId = hasSignedMarks
                // If a signed mark was provided, then it must match the desired domain label. Get the mark
                // at this point so that we can verify it before the "after validation" extension point.
                ? tmchUtils.verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now).getId()
                : null;
        customLogic.afterValidation(
                DomainCreateFlowCustomLogic.AfterValidationParameters.newBuilder().setDomainName(domainName)
                        .setYears(years).setSignedMarkId(Optional.fromNullable(signedMarkId)).build());
        FeeCreateCommandExtension feeCreate = eppInput.getSingleExtension(FeeCreateCommandExtension.class);
        FeesAndCredits feesAndCredits = pricingLogic.getCreatePrice(registry, targetId, now, years);
        validateFeeChallenge(targetId, registry.getTldStr(), now, feeCreate, feesAndCredits);
        SecDnsCreateExtension secDnsCreate = validateSecDnsExtension(
                eppInput.getSingleExtension(SecDnsCreateExtension.class));
        String repoId = createDomainRepoId(ObjectifyService.allocateId(), registry.getTldStr());
        DateTime registrationExpirationTime = leapSafeAddYears(now, years);
        HistoryEntry historyEntry = buildHistory(repoId, period, now);
        // Bill for the create.
        BillingEvent.OneTime createBillingEvent = createOneTimeBillingEvent(registry, isAnchorTenant, years,
                feesAndCredits, historyEntry, now);
        // Create a new autorenew billing event and poll message starting at the expiration time.
        BillingEvent.Recurring autorenewBillingEvent = createAutorenewBillingEvent(historyEntry,
                registrationExpirationTime);
        PollMessage.Autorenew autorenewPollMessage = createAutorenewPollMessage(historyEntry,
                registrationExpirationTime);
        ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
        entitiesToSave.add(historyEntry, createBillingEvent, autorenewBillingEvent, autorenewPollMessage);
        // Bill for EAP cost, if any.
        if (!feesAndCredits.getEapCost().isZero()) {
            entitiesToSave.add(createEapBillingEvent(feesAndCredits, createBillingEvent));
        }
        DomainResource newDomain = new DomainResource.Builder().setCreationClientId(clientId)
                .setCurrentSponsorClientId(clientId).setRepoId(repoId)
                .setIdnTableName(validateDomainNameWithIdnTables(domainName))
                .setRegistrationExpirationTime(registrationExpirationTime)
                .setAutorenewBillingEvent(Key.create(autorenewBillingEvent))
                .setAutorenewPollMessage(Key.create(autorenewPollMessage))
                .setLaunchNotice(hasClaimsNotice ? launchCreate.getNotice() : null).setSmdId(signedMarkId)
                .setDsData(secDnsCreate == null ? null : secDnsCreate.getDsData())
                .setRegistrant(command.getRegistrant()).setAuthInfo(command.getAuthInfo())
                .setFullyQualifiedDomainName(targetId).setNameservers(command.getNameservers())
                .setContacts(command.getContacts())
                .addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createBillingEvent)).build();
        entitiesToSave.add(newDomain, ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()),
                EppResourceIndex.create(Key.create(newDomain)));

        // Anchor tenant registrations override LRP, and landrush applications can skip it.
        // If a token is passed in outside of an LRP phase, it is simply ignored (i.e. never redeemed).
        if (isLrpCreate(registry, isAnchorTenant, now)) {
            entitiesToSave.add(prepareMarkedLrpTokenEntity(authInfo.getPw().getValue(), domainName, historyEntry));
        }
        enqueueTasks(isSunriseCreate, hasClaimsNotice, newDomain);

        EntityChanges entityChanges = customLogic.beforeSave(DomainCreateFlowCustomLogic.BeforeSaveParameters
                .newBuilder().setNewDomain(newDomain).setHistoryEntry(historyEntry)
                .setEntityChanges(EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build())
                .setYears(years).build());
        persistEntityChanges(entityChanges);

        BeforeResponseReturnData responseData = customLogic.beforeResponse(BeforeResponseParameters.newBuilder()
                .setResData(DomainCreateData.create(targetId, now, registrationExpirationTime))
                .setResponseExtensions(createResponseExtensions(feeCreate, feesAndCredits)).build());
        return responseBuilder.setResData(responseData.resData()).setExtensions(responseData.responseExtensions())
                .build();
    }

    private boolean isAnchorTenant(InternetDomainName domainName) {
        MetadataExtension metadataExtension = eppInput.getSingleExtension(MetadataExtension.class);
        return matchesAnchorTenantReservation(domainName, authInfo.getPw().getValue())
                || (metadataExtension != null && metadataExtension.getIsAnchorTenant());
    }

    /** Prohibit creating a domain if there is an open application for the same name. */
    private void verifyNoOpenApplications(DateTime now) throws DomainHasOpenApplicationsException {
        for (DomainApplication application : loadActiveApplicationsByDomainName(targetId, now)) {
            if (!application.getApplicationStatus().isFinalStatus()) {
                throw new DomainHasOpenApplicationsException();
            }
        }
    }

    /** Prohibit registrations for non-qlp and non-superuser outside of GA. **/
    private void verifyIsGaOrIsSpecialCase(TldState tldState, boolean isAnchorTenant)
            throws NoGeneralRegistrationsInCurrentPhaseException {
        if (!isAnchorTenant && tldState != TldState.GENERAL_AVAILABILITY) {
            throw new NoGeneralRegistrationsInCurrentPhaseException();
        }
    }

    private HistoryEntry buildHistory(String repoId, Period period, DateTime now) {
        return historyBuilder.setType(HistoryEntry.Type.DOMAIN_CREATE).setPeriod(period).setModificationTime(now)
                .setParent(Key.create(DomainResource.class, repoId)).build();
    }

    private OneTime createOneTimeBillingEvent(Registry registry, boolean isAnchorTenant, int years,
            FeesAndCredits feesAndCredits, HistoryEntry historyEntry, DateTime now) {
        return new BillingEvent.OneTime.Builder().setReason(Reason.CREATE).setTargetId(targetId)
                .setClientId(clientId).setPeriodYears(years).setCost(feesAndCredits.getCreateCost())
                .setEventTime(now)
                .setBillingTime(now.plus(isAnchorTenant ? registry.getAnchorTenantAddGracePeriodLength()
                        : registry.getAddGracePeriodLength()))
                .setFlags(isAnchorTenant ? ImmutableSet.of(BillingEvent.Flag.ANCHOR_TENANT)
                        : ImmutableSet.<BillingEvent.Flag>of())
                .setParent(historyEntry).build();
    }

    private Recurring createAutorenewBillingEvent(HistoryEntry historyEntry, DateTime registrationExpirationTime) {
        return new BillingEvent.Recurring.Builder().setReason(Reason.RENEW)
                .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)).setTargetId(targetId).setClientId(clientId)
                .setEventTime(registrationExpirationTime).setRecurrenceEndTime(END_OF_TIME).setParent(historyEntry)
                .build();
    }

    private Autorenew createAutorenewPollMessage(HistoryEntry historyEntry, DateTime registrationExpirationTime) {
        return new PollMessage.Autorenew.Builder().setTargetId(targetId).setClientId(clientId)
                .setEventTime(registrationExpirationTime).setMsg("Domain was auto-renewed.").setParent(historyEntry)
                .build();
    }

    private static OneTime createEapBillingEvent(FeesAndCredits feesAndCredits,
            BillingEvent.OneTime createBillingEvent) {
        return new BillingEvent.OneTime.Builder().setReason(Reason.FEE_EARLY_ACCESS)
                .setTargetId(createBillingEvent.getTargetId()).setClientId(createBillingEvent.getClientId())
                .setCost(feesAndCredits.getEapCost()).setEventTime(createBillingEvent.getEventTime())
                .setBillingTime(createBillingEvent.getBillingTime()).setFlags(createBillingEvent.getFlags())
                .setParent(createBillingEvent.getParentKey()).build();
    }

    private boolean isLrpCreate(Registry registry, boolean isAnchorTenant, DateTime now) {
        return registry.getLrpPeriod().contains(now) && !isAnchorTenant;
    }

    private void enqueueTasks(boolean isSunriseCreate, boolean hasClaimsNotice, DomainResource newDomain) {
        if (newDomain.shouldPublishToDns()) {
            dnsQueue.addDomainRefreshTask(newDomain.getFullyQualifiedDomainName());
        }
        if (hasClaimsNotice || isSunriseCreate) {
            LordnTask.enqueueDomainResourceTask(newDomain);
        }
    }

    private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
            FeeCreateCommandExtension feeCreate, FeesAndCredits feesAndCredits) {
        return (feeCreate == null) ? ImmutableList.<FeeTransformResponseExtension>of()
                : ImmutableList.of(createFeeCreateResponse(feeCreate, feesAndCredits));
    }

    /** There is an open application for this domain. */
    static class DomainHasOpenApplicationsException extends StatusProhibitsOperationException {
        public DomainHasOpenApplicationsException() {
            super("There is an open application for this domain");
        }
    }

    /** The current registry phase does not allow for general registrations. */
    static class NoGeneralRegistrationsInCurrentPhaseException extends CommandUseErrorException {
        public NoGeneralRegistrationsInCurrentPhaseException() {
            super("The current registry phase does not allow for general registrations");
        }
    }
}