diff --git a/java/google/registry/flows/EppException.java b/java/google/registry/flows/EppException.java index 405810d18..345e8ca4d 100644 --- a/java/google/registry/flows/EppException.java +++ b/java/google/registry/flows/EppException.java @@ -250,4 +250,12 @@ public abstract class EppException extends Exception { super("Specified protocol version is not implemented"); } } + + /** Specified protocol version is not implemented. */ + @EppResultCode(Code.CommandFailed) + public static class CommandFailedException extends EppException { + public CommandFailedException() { + super("Command failed"); + } + } } diff --git a/java/google/registry/flows/ResourceSyncDeleteFlow.java b/java/google/registry/flows/ResourceSyncDeleteFlow.java index 974a3dcfa..31171ae92 100644 --- a/java/google/registry/flows/ResourceSyncDeleteFlow.java +++ b/java/google/registry/flows/ResourceSyncDeleteFlow.java @@ -36,7 +36,7 @@ public abstract class ResourceSyncDeleteFlow @Override @SuppressWarnings("unchecked") - protected final R createOrMutateResource() { + protected final R createOrMutateResource() throws EppException { B builder = (B) prepareDeletedResourceAsBuilder(existingResource, now); setDeleteProperties(builder); return builder.build(); @@ -52,7 +52,7 @@ public abstract class ResourceSyncDeleteFlow /** Set any resource-specific properties before deleting. */ @SuppressWarnings("unused") - protected void setDeleteProperties(B builder) {} + protected void setDeleteProperties(B builder) throws EppException {} /** Modify any other resources that need to be informed of this delete. */ protected void modifySyncDeleteRelatedResources() {} diff --git a/java/google/registry/flows/ResourceTransferRequestFlow.java b/java/google/registry/flows/ResourceTransferRequestFlow.java index 01a75fdb7..4b60f7d70 100644 --- a/java/google/registry/flows/ResourceTransferRequestFlow.java +++ b/java/google/registry/flows/ResourceTransferRequestFlow.java @@ -77,7 +77,7 @@ import org.joda.time.Duration; }}; @Override - protected final void initResourceCreateOrMutateFlow() { + protected final void initResourceCreateOrMutateFlow() throws EppException { initResourceTransferRequestFlow(); } @@ -100,7 +100,8 @@ import org.joda.time.Duration; verifyTransferRequestIsAllowed(); } - private TransferData.Builder createTransferDataBuilder(TransferStatus transferStatus) { + private TransferData.Builder + createTransferDataBuilder(TransferStatus transferStatus) throws EppException { TransferData.Builder builder = new TransferData.Builder() .setGainingClientId(gainingClient.getId()) .setTransferRequestTime(now) @@ -113,7 +114,7 @@ import org.joda.time.Duration; } private PollMessage createPollMessage( - Client client, TransferStatus transferStatus, DateTime eventTime) { + Client client, TransferStatus transferStatus, DateTime eventTime) throws EppException { ImmutableList.Builder responseData = new ImmutableList.Builder<>(); responseData.add(createTransferResponse( existingResource, createTransferDataBuilder(transferStatus).build(), now)); @@ -132,7 +133,7 @@ import org.joda.time.Duration; @Override @SuppressWarnings("unchecked") - protected final R createOrMutateResource() { + protected final R createOrMutateResource() throws EppException { // Figure out transfer expiration time once we've verified that the existingResource does in // fact exist (otherwise we won't know which TLD to get this figure off of). transferExpirationTime = now.plus(getAutomaticTransferLength()); @@ -158,7 +159,7 @@ import org.joda.time.Duration; } /** Subclasses can override this to do further initialization. */ - protected void initResourceTransferRequestFlow() {} + protected void initResourceTransferRequestFlow() throws EppException {} /** * Subclasses can override this to return the keys of any entities that need to be deleted if the @@ -173,8 +174,8 @@ import org.joda.time.Duration; protected void verifyTransferRequestIsAllowed() throws EppException {} /** Subclasses can override this to modify fields on the transfer data builder. */ - protected void setTransferDataProperties( - @SuppressWarnings("unused") TransferData.Builder builder) {} + @SuppressWarnings("unused") + protected void setTransferDataProperties(TransferData.Builder builder) throws EppException {} @Override protected final EppOutput getOutput() throws EppException { diff --git a/java/google/registry/flows/domain/BaseDomainCreateFlow.java b/java/google/registry/flows/domain/BaseDomainCreateFlow.java index f481f8d44..3b619a561 100644 --- a/java/google/registry/flows/domain/BaseDomainCreateFlow.java +++ b/java/google/registry/flows/domain/BaseDomainCreateFlow.java @@ -51,12 +51,14 @@ import google.registry.flows.EppException.StatusProhibitsOperationException; import google.registry.flows.EppException.UnimplementedOptionException; import google.registry.flows.ResourceCreateFlow; import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException; +import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase.Builder; import google.registry.model.domain.DomainCommand.Create; import google.registry.model.domain.DomainResource; import google.registry.model.domain.LrpToken; import google.registry.model.domain.fee.FeeTransformCommandExtension; +import google.registry.model.domain.flags.FlagsCreateCommandExtension; import google.registry.model.domain.launch.LaunchCreateExtension; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException; @@ -67,8 +69,6 @@ import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.smd.SignedMark; import google.registry.model.tmch.ClaimsListShard; -import google.registry.pricing.TldSpecificLogicProxy; -import google.registry.pricing.TldSpecificLogicProxy.EppCommandOperations; import java.util.Set; import javax.annotation.Nullable; @@ -95,16 +95,20 @@ public abstract class BaseDomainCreateFlow lrpToken; + protected Optional extraFlowLogic; + @Override public final void initResourceCreateOrMutateFlow() throws EppException { command = cloneAndLinkReferences(command, now); - registerExtensions(SecDnsCreateExtension.class); + registerExtensions(SecDnsCreateExtension.class, FlagsCreateCommandExtension.class); + registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); secDnsCreate = eppInput.getSingleExtension(SecDnsCreateExtension.class); launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class); feeCreate = eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty(); initDomainCreateFlow(); + // We can't initialize extraFlowLogic here, because the TLD has not been checked yet. } @Override @@ -181,9 +185,19 @@ public abstract class BaseDomainCreateFlow credits; + protected Optional extraFlowLogic; + @Inject DomainDeleteFlow() {} @Override protected void initResourceCreateOrMutateFlow() throws EppException { registerExtensions(SecDnsUpdateExtension.class); + extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource); } @Override @@ -93,7 +98,7 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow builder, getTargetId(), existingResource.getTld(), + getClientId(), null, - now); + now, + eppInput); extensions.add(builder.build()); } // If the TLD uses the flags extension, add it to the info response. Optional extraLogicManager = - RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld()); + RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource); if (extraLogicManager.isPresent()) { List flags = extraLogicManager.get().getExtensionFlags( existingResource, this.getClientId(), now); // As-of date is always now for info commands. diff --git a/java/google/registry/flows/domain/DomainRenewFlow.java b/java/google/registry/flows/domain/DomainRenewFlow.java index ff9775194..9d16f43c6 100644 --- a/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/java/google/registry/flows/domain/DomainRenewFlow.java @@ -28,6 +28,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; 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.googlecode.objectify.Key; @@ -84,6 +85,8 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow extraFlowLogic; + @Inject DomainRenewFlow() {} @Override @@ -96,6 +99,7 @@ public class DomainRenewFlow extends OwnedResourceMutateFlowentities(explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage); return existingResource.asBuilder() .setRegistrationExpirationTime(newExpirationTime) @@ -160,6 +171,14 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow extraFlowLogic; @Inject DomainRestoreRequestFlow() {} @@ -82,6 +84,7 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlowentities(restoreEvent, autorenewEvent, autorenewPollMessage, renewEvent); + + // Handle extra flow logic, if any. + if (extraFlowLogic.isPresent()) { + extraFlowLogic.get().performAdditionalDomainRestoreLogic( + existingResource, getClientId(), now, eppInput); + } + return existingResource.asBuilder() .setRegistrationExpirationTime(newExpirationTime) .setDeletionTime(END_OF_TIME) @@ -171,6 +181,10 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow extraFlowLogic; /** * An optional extension from the client specifying how much they think the transfer should cost. @@ -101,7 +106,8 @@ public class DomainTransferRequestFlow } @Override - protected final void initResourceTransferRequestFlow() { + protected final void initResourceTransferRequestFlow() throws EppException { + registerExtensions(FlagsTransferCommandExtension.class); registerExtensions(FEE_TRANSFER_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); feeTransfer = eppInput.getFirstExtensionOfClasses( FEE_TRANSFER_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER); @@ -146,6 +152,7 @@ public class DomainTransferRequestFlow .setMsg("Domain was auto-renewed.") .setParent(historyEntry) .build(); + extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource); } @Override @@ -174,12 +181,18 @@ public class DomainTransferRequestFlow } @Override - protected void setTransferDataProperties(TransferData.Builder builder) { + protected void setTransferDataProperties(TransferData.Builder builder) throws EppException { builder .setServerApproveBillingEvent(Key.create(transferBillingEvent)) .setServerApproveAutorenewEvent(Key.create(gainingClientAutorenewEvent)) .setServerApproveAutorenewPollMessage(Key.create(gainingClientAutorenewPollMessage)) .setExtendedRegistrationYears(command.getPeriod().getValue()); + + // Handle extra flow logic, if any. + if (extraFlowLogic.isPresent()) { + extraFlowLogic.get().performAdditionalDomainTransferLogic( + existingResource, getClientId(), now, command.getPeriod().getValue(), eppInput); + } } /** @@ -233,6 +246,10 @@ public class DomainTransferRequestFlow // transfer occurs, then the logic in cloneProjectedAtTime() will move the // serverApproveAutoRenewEvent into the autoRenewEvent field. updateAutorenewRecurrenceEndTime(existingResource, automaticTransferTime); + + if (extraFlowLogic.isPresent()) { + extraFlowLogic.get().commitAdditionalLogicChanges(); + } } @Override diff --git a/java/google/registry/flows/domain/DomainUpdateFlow.java b/java/google/registry/flows/domain/DomainUpdateFlow.java index c1991f825..f8f22e6ef 100644 --- a/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -138,7 +138,7 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow getExtensionFlags( DomainResource domainResource, String clientIdentifier, DateTime asOfDate); + /** Computes the expected creation fee, for use in fee challenges and the like. */ + public BaseFee getCreateFeeOrCredit( + String domainName, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException; + /** - * Add and remove flags passed via the EPP flags extension. Any changes should not be persisted to - * Datastore until commitAdditionalDomainUpdates is called. Name suggested by Benjamin McIlwain. + * Performs additional tasks required for a create command. Any changes should not be persisted to + * Datastore until commitAdditionalLogicChanges is called. */ - public void performAdditionalDomainUpdateLogic( - DomainResource domainResource, + public void performAdditionalDomainCreateLogic( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException; + + /** + * Performs additional tasks required for a delete command. Any changes should not be persisted to + * Datastore until commitAdditionalLogicChanges is called. + */ + public void performAdditionalDomainDeleteLogic( + DomainResource domain, String clientIdentifier, DateTime asOfDate, EppInput eppInput) throws EppException; - /** Commit any changes made as a result of a call to performAdditionalDomainUpdateLogic(). */ - public void commitAdditionalDomainUpdates(); + /** Computes the expected renewal fee, for use in fee challenges and the like. */ + public BaseFee getRenewFeeOrCredit( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException; + + /** + * Performs additional tasks required for a renew command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + public void performAdditionalDomainRenewLogic( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException; + + /** + * Performs additional tasks required for a restore command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + public void performAdditionalDomainRestoreLogic( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException; + + /** + * Performs additional tasks required for a transfer command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + public void performAdditionalDomainTransferLogic( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException; + + /** Computes the expected update fee, for use in fee challenges and the like. */ + public BaseFee getUpdateFeeOrCredit( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException; + + /** + * Performs additional tasks required for an update command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + public void performAdditionalDomainUpdateLogic( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException; + + /** Commits any changes made as a result of a call to one of the performXXX methods. */ + public void commitAdditionalLogicChanges(); } diff --git a/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java b/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java index 1f5a7f148..b1f7f815e 100644 --- a/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java +++ b/java/google/registry/flows/domain/RegistryExtraFlowLogicProxy.java @@ -15,8 +15,12 @@ package google.registry.flows.domain; import com.google.common.base.Optional; +import google.registry.flows.EppException; +import google.registry.flows.EppException.CommandFailedException; +import google.registry.model.domain.DomainBase; import google.registry.model.registry.Registry; import java.util.HashMap; +import javax.annotation.Nullable; /** * Static class to return the correct {@link RegistryExtraFlowLogic} for a particular TLD. @@ -36,12 +40,23 @@ public class RegistryExtraFlowLogicProxy { extraLogicOverrideMap.put(tld, extraLogicClass); } - public static Optional newInstanceForTld(String tld) { + public static Optional + newInstanceForDomain(@Nullable D domain) throws EppException { + if (domain == null) { + return Optional.absent(); + } else { + return newInstanceForTld(domain.getTld()); + } + } + + public static Optional + newInstanceForTld(String tld) throws EppException { if (extraLogicOverrideMap.containsKey(tld)) { try { - return Optional.of(extraLogicOverrideMap.get(tld).newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - return Optional.absent(); + return Optional.of( + extraLogicOverrideMap.get(tld).getConstructor().newInstance()); + } catch (ReflectiveOperationException ex) { + throw new CommandFailedException(); } } return Optional.absent(); diff --git a/java/google/registry/flows/domain/TldSpecificLogicProxy.java b/java/google/registry/flows/domain/TldSpecificLogicProxy.java new file mode 100644 index 000000000..ee7ae0629 --- /dev/null +++ b/java/google/registry/flows/domain/TldSpecificLogicProxy.java @@ -0,0 +1,297 @@ +// Copyright 2016 The Domain Registry 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.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static google.registry.model.EppResourceUtils.loadByUniqueId; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName; +import static google.registry.util.CollectionUtils.nullToEmpty; +import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.Key; +import google.registry.flows.EppException; +import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException; +import google.registry.model.ImmutableObject; +import google.registry.model.domain.DomainCommand.Create; +import google.registry.model.domain.DomainResource; +import google.registry.model.domain.LrpToken; +import google.registry.model.domain.fee.BaseFee; +import google.registry.model.domain.fee.BaseFee.FeeType; +import google.registry.model.domain.fee.Credit; +import google.registry.model.domain.fee.EapFee; +import google.registry.model.domain.fee.Fee; +import google.registry.model.eppinput.EppInput; +import google.registry.model.pricing.PremiumPricingEngine.DomainPrices; +import google.registry.model.registry.Registry; +import java.util.List; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.joda.time.DateTime; + +/** + * Provides pricing, billing, and update logic, with call-outs that can be customized by providing + * implementations on a per-TLD basis. + */ +public final class TldSpecificLogicProxy { + /** A collection of fees and credits for a specific EPP transform. */ + public static final class EppCommandOperations extends ImmutableObject { + private final CurrencyUnit currency; + private final ImmutableList fees; + private final ImmutableList credits; + + /** Constructs an EppCommandOperations object using separate lists of fees and credits. */ + EppCommandOperations( + CurrencyUnit currency, ImmutableList fees, ImmutableList credits) { + this.currency = checkArgumentNotNull( + currency, "Currency may not be null in EppCommandOperations."); + checkArgument(!fees.isEmpty(), "You must specify one or more fees."); + this.fees = checkArgumentNotNull(fees, "Fees may not be null in EppCommandOperations."); + this.credits = + checkArgumentNotNull(credits, "Credits may not be null in EppCommandOperations."); + } + + /** + * Constructs an EppCommandOperations object. The arguments are sorted into fees and credits. + */ + EppCommandOperations(CurrencyUnit currency, BaseFee... feesAndCredits) { + this.currency = checkArgumentNotNull( + currency, "Currency may not be null in EppCommandOperations."); + ImmutableList.Builder feeBuilder = new ImmutableList.Builder<>(); + ImmutableList.Builder creditBuilder = new ImmutableList.Builder<>(); + for (BaseFee feeOrCredit : feesAndCredits) { + if (feeOrCredit instanceof Credit) { + creditBuilder.add((Credit) feeOrCredit); + } else { + feeBuilder.add((Fee) feeOrCredit); + } + } + this.fees = feeBuilder.build(); + this.credits = creditBuilder.build(); + } + + private Money getTotalCostForType(FeeType type) { + Money result = Money.zero(currency); + checkArgumentNotNull(type); + for (Fee fee : fees) { + if (fee.getType() == type) { + result = result.plus(fee.getCost()); + } + } + return result; + } + + /** Returns the total cost of all fees and credits for the event. */ + public Money getTotalCost() { + Money result = Money.zero(currency); + for (Fee fee : fees) { + result = result.plus(fee.getCost()); + } + for (Credit credit : credits) { + result = result.plus(credit.getCost()); + } + return result; + } + + /** Returns the create cost for the event. */ + public Money getCreateCost() { + return getTotalCostForType(FeeType.CREATE); + } + + /** Returns the EAP cost for the event. */ + public Money getEapCost() { + return getTotalCostForType(FeeType.EAP); + } + + /** Returns the list of fees for the event. */ + public ImmutableList getFees() { + return fees; + } + + /** Returns the list of credits for the event. */ + public List getCredits() { + return nullToEmpty(credits); + } + + /** Returns the currency for all fees in the event. */ + public final CurrencyUnit getCurrency() { + return currency; + } + } + + private TldSpecificLogicProxy() {} + + /** Returns a new create price for the Pricer. */ + public static EppCommandOperations getCreatePrice( + Registry registry, + String domainName, + String clientIdentifier, + DateTime date, + int years, + EppInput eppInput) throws EppException { + CurrencyUnit currency = registry.getCurrency(); + + // Get the create cost, either from the extra flow logic or straight from PricingEngineProxy. + BaseFee createFeeOrCredit; + Optional extraFlowLogic = + RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr()); + if (extraFlowLogic.isPresent()) { + createFeeOrCredit = extraFlowLogic.get() + .getCreateFeeOrCredit(domainName, clientIdentifier, date, years, eppInput); + } else { + DomainPrices prices = getPricesForDomainName(domainName, date); + createFeeOrCredit = + Fee.create(prices.getCreateCost().multipliedBy(years).getAmount(), FeeType.CREATE); + } + + // Create fees for the cost and the EAP fee, if any. + EapFee eapFee = registry.getEapFeeFor(date); + Money eapFeeCost = eapFee.getCost(); + checkState(eapFeeCost.getCurrencyUnit().equals(currency)); + if (!eapFeeCost.getAmount().equals(Money.zero(currency).getAmount())) { + return new EppCommandOperations( + currency, + createFeeOrCredit, + Fee.create(eapFeeCost.getAmount(), FeeType.EAP, eapFee.getPeriod().upperEndpoint())); + } else { + return new EppCommandOperations(currency, createFeeOrCredit); + } + } + + /** + * Computes the renew fee or credit. This is called by other methods which use the renew fee + * (renew, restore, etc). + */ + static BaseFee getRenewFeeOrCredit( + Registry registry, + String domainName, + String clientIdentifier, + DateTime date, + int years, + EppInput eppInput) throws EppException { + Optional extraFlowLogic = + RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr()); + if (extraFlowLogic.isPresent()) { + // TODO: Consider changing the method definition to have the domain passed in to begin with. + DomainResource domain = loadByUniqueId(DomainResource.class, domainName, date); + if (domain == null) { + throw new ResourceToMutateDoesNotExistException(DomainResource.class, domainName); + } + return + extraFlowLogic.get().getRenewFeeOrCredit(domain, clientIdentifier, date, years, eppInput); + } else { + DomainPrices prices = getPricesForDomainName(domainName, date); + return Fee.create(prices.getRenewCost().multipliedBy(years).getAmount(), FeeType.RENEW); + } + } + + /** Returns a new renew price for the pricer. */ + public static EppCommandOperations getRenewPrice( + Registry registry, + String domainName, + String clientIdentifier, + DateTime date, + int years, + EppInput eppInput) throws EppException { + return new EppCommandOperations( + registry.getCurrency(), + getRenewFeeOrCredit(registry, domainName, clientIdentifier, date, years, eppInput)); + } + + /** Returns a new restore price for the pricer. */ + public static EppCommandOperations getRestorePrice( + Registry registry, + String domainName, + String clientIdentifier, + DateTime date, + EppInput eppInput) throws EppException { + return new EppCommandOperations( + registry.getCurrency(), + getRenewFeeOrCredit(registry, domainName, clientIdentifier, date, 1, eppInput), + Fee.create(registry.getStandardRestoreCost().getAmount(), FeeType.RESTORE)); + } + + /** Returns a new transfer price for the pricer. */ + public static EppCommandOperations getTransferPrice( + Registry registry, + String domainName, + String clientIdentifier, + DateTime transferDate, + int years, + EppInput eppInput) throws EppException { + // Currently, all transfer prices = renew prices, so just pass through. + return getRenewPrice( + registry, domainName, clientIdentifier, transferDate, years, eppInput); + } + + /** Returns a new update price for the pricer. */ + public static EppCommandOperations getUpdatePrice( + Registry registry, + String domainName, + String clientIdentifier, + DateTime date, + EppInput eppInput) throws EppException { + CurrencyUnit currency = registry.getCurrency(); + + // If there is extra flow logic, it may specify an update price. Otherwise, there is none. + BaseFee feeOrCredit; + Optional extraFlowLogic = + RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr()); + if (extraFlowLogic.isPresent()) { + // TODO: Consider changing the method definition to have the domain passed in to begin with. + DomainResource domain = loadByUniqueId(DomainResource.class, domainName, date); + if (domain == null) { + throw new ResourceToMutateDoesNotExistException(DomainResource.class, domainName); + } + feeOrCredit = + extraFlowLogic.get().getUpdateFeeOrCredit(domain, clientIdentifier, date, eppInput); + } else { + feeOrCredit = Fee.create(Money.zero(registry.getCurrency()).getAmount(), FeeType.UPDATE); + } + + return new EppCommandOperations(currency, feeOrCredit); + } + + /** Returns the fee class for a given domain and date. */ + public static Optional getFeeClass(String domainName, DateTime date) { + return getPricesForDomainName(domainName, date).getFeeClass(); + } + + /** + * Checks whether a {@link Create} command has a valid {@link LrpToken} for a particular TLD, and + * return that token (wrapped in an {@link Optional}) if one exists. + * + *

This method has no knowledge of whether or not an auth code (interpreted here as an LRP + * token) has already been checked against the reserved list for QLP (anchor tenant), as auth + * codes are used for both types of registrations. + */ + public static Optional getMatchingLrpToken(Create createCommand, String tld) { + // Note that until the actual per-TLD logic is built out, what's being done here is a basic + // domain-name-to-assignee match. + String lrpToken = createCommand.getAuthInfo().getPw().getValue(); + LrpToken token = ofy().load().key(Key.create(LrpToken.class, lrpToken)).now(); + if (token != null) { + if (token.getAssignee().equalsIgnoreCase(createCommand.getFullyQualifiedDomainName()) + && token.getRedemptionHistoryEntry() == null + && token.getValidTlds().contains(tld)) { + return Optional.of(token); + } + } + return Optional.absent(); + } +} diff --git a/java/google/registry/model/domain/fee/BaseFee.java b/java/google/registry/model/domain/fee/BaseFee.java index 5b10e888e..bdee83ab7 100644 --- a/java/google/registry/model/domain/fee/BaseFee.java +++ b/java/google/registry/model/domain/fee/BaseFee.java @@ -45,7 +45,9 @@ public abstract class BaseFee extends ImmutableObject { CREATE("create"), EAP("Early Access Period, fee expires: %s"), RENEW("renew"), - RESTORE("restore"); + RESTORE("restore"), + UPDATE("update"), + CREDIT("%s credit"); private final String formatString; diff --git a/java/google/registry/model/domain/fee/Credit.java b/java/google/registry/model/domain/fee/Credit.java index ce64e0aac..aa34291b3 100644 --- a/java/google/registry/model/domain/fee/Credit.java +++ b/java/google/registry/model/domain/fee/Credit.java @@ -21,11 +21,12 @@ import java.math.BigDecimal; /** A credit, in currency units specified elsewhere in the xml, and with an optional description. */ public class Credit extends BaseFee { - public static Credit create(BigDecimal cost, String description) { + public static Credit create(BigDecimal cost, FeeType type, Object... descriptionArgs) { Credit instance = new Credit(); instance.cost = checkNotNull(cost); checkArgument(instance.cost.signum() < 0); - instance.description = description; + instance.type = checkNotNull(type); + instance.generateDescription(descriptionArgs); return instance; } } diff --git a/java/google/registry/model/domain/fee/FeeTransformCommandExtensionImpl.java b/java/google/registry/model/domain/fee/FeeTransformCommandExtensionImpl.java index 10204d8c5..9d7b76561 100644 --- a/java/google/registry/model/domain/fee/FeeTransformCommandExtensionImpl.java +++ b/java/google/registry/model/domain/fee/FeeTransformCommandExtensionImpl.java @@ -14,6 +14,8 @@ package google.registry.model.domain.fee; +import static google.registry.util.CollectionUtils.nullToEmpty; + import google.registry.model.ImmutableObject; import java.util.List; import javax.xml.bind.annotation.XmlElement; @@ -51,6 +53,6 @@ public abstract class FeeTransformCommandExtensionImpl @Override public List getCredits() { - return credits; + return nullToEmpty(credits); } } diff --git a/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java b/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java index c75b208f9..ead5e687f 100644 --- a/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java +++ b/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java @@ -14,8 +14,8 @@ package google.registry.model.domain.fee; -import com.google.common.collect.ImmutableList; import google.registry.model.eppoutput.EppResponse.ResponseExtension; +import java.util.List; import org.joda.money.CurrencyUnit; /** Interface for fee extensions in Create, Renew, Transfer and Update responses. */ @@ -24,8 +24,8 @@ public interface FeeTransformResponseExtension extends ResponseExtension { /** Builder for {@link FeeTransformResponseExtension}. */ public interface Builder { Builder setCurrency(CurrencyUnit currency); - Builder setFees(ImmutableList fees); - Builder setCredits(ImmutableList credits); + Builder setFees(List fees); + Builder setCredits(List credits); FeeTransformResponseExtension build(); } } diff --git a/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImpl.java b/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImpl.java index 8c443c25b..855877292 100644 --- a/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImpl.java +++ b/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImpl.java @@ -14,7 +14,8 @@ package google.registry.model.domain.fee; -import com.google.common.collect.ImmutableList; +import static google.registry.util.CollectionUtils.forceEmptyToNull; + import google.registry.model.Buildable.GenericBuilder; import google.registry.model.ImmutableObject; import java.util.List; @@ -53,14 +54,14 @@ public class FeeTransformResponseExtensionImpl extends ImmutableObject } @Override - public B setFees(ImmutableList fees) { + public B setFees(List fees) { getInstance().fees = fees; return thisCastToDerived(); } @Override - public B setCredits(ImmutableList credits) { - getInstance().credits = credits; + public B setCredits(List credits) { + getInstance().credits = forceEmptyToNull(credits); return thisCastToDerived(); } } diff --git a/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImplNoCredits.java b/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImplNoCredits.java index c5f67f285..99f901057 100644 --- a/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImplNoCredits.java +++ b/java/google/registry/model/domain/fee/FeeTransformResponseExtensionImplNoCredits.java @@ -14,7 +14,6 @@ package google.registry.model.domain.fee; -import com.google.common.collect.ImmutableList; import google.registry.model.Buildable.GenericBuilder; import google.registry.model.ImmutableObject; import java.util.List; @@ -54,13 +53,13 @@ public class FeeTransformResponseExtensionImplNoCredits extends ImmutableObject } @Override - public B setFees(ImmutableList fees) { + public B setFees(List fees) { getInstance().fees = fees; return thisCastToDerived(); } @Override - public B setCredits(ImmutableList credits) { + public B setCredits(List credits) { return thisCastToDerived(); } } diff --git a/java/google/registry/model/domain/flags/FlagsCreateCommandExtension.java b/java/google/registry/model/domain/flags/FlagsCreateCommandExtension.java index fadf94107..e73a618c2 100644 --- a/java/google/registry/model/domain/flags/FlagsCreateCommandExtension.java +++ b/java/google/registry/model/domain/flags/FlagsCreateCommandExtension.java @@ -30,4 +30,8 @@ import javax.xml.bind.annotation.XmlRootElement; public class FlagsCreateCommandExtension implements CommandExtension { @XmlElement(name = "flag") List flags; + + public List getFlags() { + return flags; + } } diff --git a/java/google/registry/model/domain/flags/FlagsTransferCommandExtension.java b/java/google/registry/model/domain/flags/FlagsTransferCommandExtension.java index c55ed796a..84088c365 100644 --- a/java/google/registry/model/domain/flags/FlagsTransferCommandExtension.java +++ b/java/google/registry/model/domain/flags/FlagsTransferCommandExtension.java @@ -30,4 +30,12 @@ import javax.xml.bind.annotation.XmlType; public class FlagsTransferCommandExtension implements CommandExtension { FlagsList add; // list of flags to be added (turned on) FlagsList rem; // list of flags to be removed (turned off) + + public FlagsList getAddFlags() { + return add; + } + + public FlagsList getRemoveFlags() { + return rem; + } } diff --git a/java/google/registry/pricing/TldSpecificLogicProxy.java b/java/google/registry/pricing/TldSpecificLogicProxy.java deleted file mode 100644 index 21d344822..000000000 --- a/java/google/registry/pricing/TldSpecificLogicProxy.java +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2016 The Domain Registry 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.pricing; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName; -import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; - -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableList; -import com.googlecode.objectify.Key; -import google.registry.model.ImmutableObject; -import google.registry.model.domain.DomainCommand.Create; -import google.registry.model.domain.LrpToken; -import google.registry.model.domain.fee.BaseFee.FeeType; -import google.registry.model.domain.fee.EapFee; -import google.registry.model.domain.fee.Fee; -import google.registry.model.pricing.PremiumPricingEngine.DomainPrices; -import google.registry.model.registry.Registry; -import org.joda.money.CurrencyUnit; -import org.joda.money.Money; -import org.joda.time.DateTime; - -/** - * Provides pricing, billing, and update logic, with call-outs that can be customized by providing - * implementations on a per-TLD basis. - */ -public final class TldSpecificLogicProxy { - /** - * A collection of fees for a specific event. - */ - public static final class EppCommandOperations extends ImmutableObject { - private final CurrencyUnit currency; - private final ImmutableList fees; - - EppCommandOperations(CurrencyUnit currency, ImmutableList fees) { - this.currency = checkArgumentNotNull( - currency, "Currency may not be null in EppCommandOperations."); - checkArgument(!fees.isEmpty(), "You must specify one or more fees."); - this.fees = checkArgumentNotNull(fees, "Fees may not be null in EppCommandOperations."); - } - - private Money getTotalCostForType(FeeType type) { - Money result = Money.zero(currency); - checkArgumentNotNull(type); - for (Fee fee : fees) { - if (fee.getType() == type) { - result = result.plus(fee.getCost()); - } - } - return result; - } - - /** Returns the total cost of all fees for the event. */ - public Money getTotalCost() { - Money result = Money.zero(currency); - for (Fee fee : fees) { - result = result.plus(fee.getCost()); - } - return result; - } - - /** Returns the create cost for the event. */ - public Money getCreateCost() { - return getTotalCostForType(FeeType.CREATE); - } - - /** Returns the EAP cost for the event. */ - public Money getEapCost() { - return getTotalCostForType(FeeType.EAP); - } - - /** - * Returns all costs for the event as a list of fees. - */ - public ImmutableList getFees() { - return fees; - } - - /** - * Returns the currency for all fees in the event. - */ - public final CurrencyUnit getCurrency() { - return currency; - } - } - - private TldSpecificLogicProxy() {} - - /** - * Returns a new "create" price for the Pricer. - */ - public static EppCommandOperations getCreatePrice( - Registry registry, String domainName, DateTime date, int years) { - DomainPrices prices = getPricesForDomainName(domainName, date); - CurrencyUnit currency = registry.getCurrency(); - ImmutableList.Builder feeBuilder = new ImmutableList.Builder<>(); - - // Add Create cost. - feeBuilder.add( - Fee.create(prices.getCreateCost().multipliedBy(years).getAmount(), FeeType.CREATE)); - - // Add EAP Fee. - EapFee eapFee = registry.getEapFeeFor(date); - Money eapFeeCost = eapFee.getCost(); - checkState(eapFeeCost.getCurrencyUnit().equals(currency)); - if (!eapFeeCost.getAmount().equals(Money.zero(currency).getAmount())) { - feeBuilder.add( - Fee.create( - eapFeeCost.getAmount(), FeeType.EAP, eapFee.getPeriod().upperEndpoint())); - } - - return new EppCommandOperations(currency, feeBuilder.build()); - } - - /** - * Returns a new renew price for the pricer. - */ - public static EppCommandOperations getRenewPrice( - Registry registry, String domainName, DateTime date, int years) { - DomainPrices prices = getPricesForDomainName(domainName, date); - return new EppCommandOperations( - registry.getCurrency(), - ImmutableList.of( - Fee.create( - prices.getRenewCost().multipliedBy(years).getAmount(), FeeType.RENEW))); - } - - /** - * Returns a new restore price for the pricer. - */ - public static EppCommandOperations getRestorePrice( - Registry registry, String domainName, DateTime date, int years) { - DomainPrices prices = getPricesForDomainName(domainName, date); - return new EppCommandOperations( - registry.getCurrency(), - ImmutableList.of( - Fee.create( - prices.getRenewCost().multipliedBy(years).getAmount(), FeeType.RENEW), - Fee.create(registry.getStandardRestoreCost().getAmount(), FeeType.RESTORE))); - } - - /** - * Returns a new transfer price for the pricer. - */ - public static EppCommandOperations getTransferPrice( - Registry registry, String domainName, DateTime transferDate, int additionalYears) { - // Currently, all transfer prices = renew prices, so just pass through. - return getRenewPrice(registry, domainName, transferDate, additionalYears); - } - - /** - * Returns the fee class for a given domain and date. - */ - public static Optional getFeeClass(String domainName, DateTime date) { - return getPricesForDomainName(domainName, date).getFeeClass(); - } - - /** - * Checks whether a {@link Create} command has a valid {@link LrpToken} for a particular TLD, and - * return that token (wrapped in an {@link Optional}) if one exists. - * - *

This method has no knowledge of whether or not an auth code (interpreted here as an LRP - * token) has already been checked against the reserved list for QLP (anchor tenant), as auth - * codes are used for both types of registrations. - */ - public static Optional getMatchingLrpToken(Create createCommand, String tld) { - // Note that until the actual per-TLD logic is built out, what's being done here is a basic - // domain-name-to-assignee match. - String lrpToken = createCommand.getAuthInfo().getPw().getValue(); - LrpToken token = ofy().load().key(Key.create(LrpToken.class, lrpToken)).now(); - if (token != null) { - if (token.getAssignee().equalsIgnoreCase(createCommand.getFullyQualifiedDomainName()) - && token.getRedemptionHistoryEntry() == null - && token.getValidTlds().contains(tld)) { - return Optional.of(token); - } - } - return Optional.absent(); - } -} diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 00205428b..cf260dda2 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -21,6 +21,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.testing.DatastoreHelper.deleteTld; import static google.registry.testing.DatastoreHelper.getHistoryEntries; import static google.registry.testing.DatastoreHelper.newContactResource; @@ -104,6 +105,7 @@ import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.LrpToken; +import google.registry.model.domain.TestExtraLogicManager; import google.registry.model.domain.launch.ApplicationStatus; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.rgp.GracePeriodStatus; @@ -134,7 +136,7 @@ public class DomainCreateFlowTest extends ResourceFlowTestCaseUSD update 1 - 11.00 + 0.00 diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_fee_multiple_commands_response_v12.xml b/javatests/google/registry/flows/domain/testdata/domain_check_fee_multiple_commands_response_v12.xml index 110835458..0f105a47c 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_check_fee_multiple_commands_response_v12.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_check_fee_multiple_commands_response_v12.xml @@ -56,7 +56,7 @@ 1 - 11.00 + 0.00 diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v11_update.xml b/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v11_update.xml index 6c99d8075..47572fef6 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v11_update.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v11_update.xml @@ -20,7 +20,7 @@ update USD 1 - 100.00 + 0.00 premium diff --git a/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v12.xml b/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v12.xml index c07f6ba64..eafbd5301 100644 --- a/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v12.xml +++ b/javatests/google/registry/flows/domain/testdata/domain_check_fee_premium_response_v12.xml @@ -60,7 +60,7 @@ 1 - 100.00 + 0.00 premium diff --git a/javatests/google/registry/flows/domain/testdata/domain_create_flags.xml b/javatests/google/registry/flows/domain/testdata/domain_create_flags.xml new file mode 100644 index 000000000..ff08adda7 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_create_flags.xml @@ -0,0 +1,32 @@ + + + + + create-42.flags + 2 + + ns1.example.net + ns2.example.net + + jd1234 + sh8013 + sh8013 + + 2fooBAR + + + + + + USD + %FEE% + + + flag1 + flag2 + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_delete_flags.xml b/javatests/google/registry/flows/domain/testdata/domain_delete_flags.xml new file mode 100644 index 000000000..fc32d3224 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_delete_flags.xml @@ -0,0 +1,11 @@ + + + + + example.flags + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_renew_flags.xml b/javatests/google/registry/flows/domain/testdata/domain_renew_flags.xml new file mode 100644 index 000000000..2784dec1b --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_renew_flags.xml @@ -0,0 +1,13 @@ + + + + + example.flags + 2000-04-03 + 5 + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_transfer_request_flags.xml b/javatests/google/registry/flows/domain/testdata/domain_transfer_request_flags.xml new file mode 100644 index 000000000..cae3be4a7 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_transfer_request_flags.xml @@ -0,0 +1,27 @@ + + + + + example.flags + 1 + + 2fooBAR + + + + + + + flag1 + flag2 + + + flag3 + flag4 + + + + ABC-12345 + + diff --git a/javatests/google/registry/flows/domain/testdata/domain_update_restore_request_flags.xml b/javatests/google/registry/flows/domain/testdata/domain_update_restore_request_flags.xml new file mode 100644 index 000000000..0927462e4 --- /dev/null +++ b/javatests/google/registry/flows/domain/testdata/domain_update_restore_request_flags.xml @@ -0,0 +1,17 @@ + + + + + example.flags + + + + + + + + + ABC-12345 + + diff --git a/javatests/google/registry/model/domain/TestExtraLogicManager.java b/javatests/google/registry/model/domain/TestExtraLogicManager.java index 146162364..fbfa4fbdd 100644 --- a/javatests/google/registry/model/domain/TestExtraLogicManager.java +++ b/javatests/google/registry/model/domain/TestExtraLogicManager.java @@ -14,13 +14,24 @@ package google.registry.model.domain; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Ascii; +import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import google.registry.flows.EppException; -import google.registry.flows.EppException.UnimplementedExtensionException; import google.registry.flows.domain.RegistryExtraFlowLogic; +import google.registry.model.domain.fee.BaseFee; +import google.registry.model.domain.fee.BaseFee.FeeType; +import google.registry.model.domain.fee.Credit; +import google.registry.model.domain.fee.Fee; +import google.registry.model.domain.flags.FlagsCreateCommandExtension; +import google.registry.model.domain.flags.FlagsTransferCommandExtension; import google.registry.model.domain.flags.FlagsUpdateCommandExtension; import google.registry.model.eppinput.EppInput; +import java.math.BigDecimal; import java.util.List; import org.joda.time.DateTime; @@ -29,6 +40,8 @@ import org.joda.time.DateTime; */ public class TestExtraLogicManager implements RegistryExtraFlowLogic { + private String messageToThrow = null; + @Override public List getExtensionFlags( DomainResource domainResource, String clientIdentifier, DateTime asOfDate) { @@ -41,23 +54,166 @@ public class TestExtraLogicManager implements RegistryExtraFlowLogic { return components.subList(1, components.size()); } + BaseFee domainNameToFeeOrCredit(String domainName) { + // The second-level domain should be of the form "description-price", where description is the + // description string of the fee or credit, and price is the price (credit if negative, fee + // otherwise). To make sure this is a valid domain name, don't use any spaces, and limit prices + // to integers. Don't use a two-character description for credits, since it is illegal to have + // both the third and fourth characters of a domain name label be hyphens. + List components = + Splitter.on('-').limit(2).splitToList( + Iterables.getFirst(Splitter.on('.').split(domainName), "")); + checkArgument(components.size() == 2, "Domain name must be of the form description-price.tld"); + int price = Integer.parseInt(components.get(1)); + if (price < 0) { + return Credit.create( + new BigDecimal(price), FeeType.valueOf(Ascii.toUpperCase(components.get(0)))); + } else { + return Fee.create( + new BigDecimal(price), FeeType.valueOf(Ascii.toUpperCase(components.get(0)))); + } + } + + /** Computes the expected create cost, for use in fee challenges and the like. */ + @Override + public BaseFee getCreateFeeOrCredit( + String domainName, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException { + return domainNameToFeeOrCredit(domainName); + } + + /** + * Performs additional tasks required for a create command. Any changes should not be persisted to + * Datastore until commitAdditionalLogicChanges is called. + */ + @Override + public void performAdditionalDomainCreateLogic( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException { + FlagsCreateCommandExtension flags = + eppInput.getSingleExtension(FlagsCreateCommandExtension.class); + if (flags == null) { + return; + } + messageToThrow = Joiner.on(',').join(flags.getFlags()); + } + + /** + * Performs additional tasks required for a delete command. Any changes should not be persisted to + * Datastore until commitAdditionalLogicChanges is called. + */ + @Override + public void performAdditionalDomainDeleteLogic( + DomainResource domainResource, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException { + messageToThrow = "deleted"; + } + + /** Computes the expected renewal cost, for use in fee challenges and the like. */ + @Override + public BaseFee getRenewFeeOrCredit( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException { + return domainNameToFeeOrCredit(domain.getFullyQualifiedDomainName()); + } + + /** + * Performs additional tasks required for a renew command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + @Override + public void performAdditionalDomainRenewLogic( + DomainResource domainResource, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException { + messageToThrow = "renewed"; + } + + /** + * Performs additional tasks required for a restore command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + @Override + public void performAdditionalDomainRestoreLogic( + DomainResource domainResource, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException { + messageToThrow = "restored"; + } + + /** + * Performs additional tasks required for a transfer command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ + @Override + public void performAdditionalDomainTransferLogic( + DomainResource domainResource, + String clientIdentifier, + DateTime asOfDate, + int years, + EppInput eppInput) throws EppException { + FlagsTransferCommandExtension flags = + eppInput.getSingleExtension(FlagsTransferCommandExtension.class); + if (flags == null) { + return; + } + messageToThrow = + "add:" + + Joiner.on(',').join(flags.getAddFlags().getFlags()) + + ";remove:" + + Joiner.on(',').join(flags.getRemoveFlags().getFlags()); + } + + /** Computes the expected update cost, for use in fee challenges and the like. */ + @Override + public BaseFee getUpdateFeeOrCredit( + DomainResource domain, + String clientIdentifier, + DateTime asOfDate, + EppInput eppInput) throws EppException { + return domainNameToFeeOrCredit(domain.getFullyQualifiedDomainName()); + } + + /** + * Performs additional tasks required for an update command. Any changes should not be persisted + * to Datastore until commitAdditionalLogicChanges is called. + */ @Override public void performAdditionalDomainUpdateLogic( DomainResource domainResource, String clientIdentifier, DateTime asOfDate, EppInput eppInput) throws EppException { - FlagsUpdateCommandExtension updateFlags = + FlagsUpdateCommandExtension flags = eppInput.getSingleExtension(FlagsUpdateCommandExtension.class); - if (updateFlags == null) { + if (flags == null) { return; } - // Throw this exception as a signal to the test that we got this far. - throw new UnimplementedExtensionException(); + messageToThrow = + "add:" + + Joiner.on(',').join(flags.getAddFlags().getFlags()) + + ";remove:" + + Joiner.on(',').join(flags.getRemoveFlags().getFlags()); } @Override - public void commitAdditionalDomainUpdates() { - return; + public void commitAdditionalLogicChanges() { + checkNotNull(messageToThrow); + // Throw a specific exception as a signal to the test code that we made it through to here. + throw new IllegalArgumentException(messageToThrow); } } diff --git a/javatests/google/registry/pricing/TldSpecificLogicProxyTest.java b/javatests/google/registry/pricing/TldSpecificLogicProxyTest.java deleted file mode 100644 index aaa0cf8e6..000000000 --- a/javatests/google/registry/pricing/TldSpecificLogicProxyTest.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2016 The Domain Registry 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.pricing; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.testing.DatastoreHelper.createTld; -import static google.registry.testing.DatastoreHelper.persistResource; -import static google.registry.util.DateTimeUtils.START_OF_TIME; -import static org.joda.money.CurrencyUnit.USD; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import google.registry.model.domain.fee.BaseFee.FeeType; -import google.registry.model.domain.fee.Fee; -import google.registry.model.ofy.Ofy; -import google.registry.model.registry.Registry; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; -import google.registry.testing.InjectRule; -import org.joda.money.Money; -import org.joda.time.DateTime; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class TldSpecificLogicProxyTest { - - @Rule - public final InjectRule inject = new InjectRule(); - - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - - final FakeClock clock = new FakeClock(DateTime.parse("2010-01-01T10:00:00Z")); - - Money basicCreateCost; - - @Before - public void before() throws Exception { - inject.setStaticField(Ofy.class, "clock", clock); - createTld("tld"); - - createTld("eap"); - DateTime a = clock.nowUtc().minusDays(1); - DateTime b = clock.nowUtc().plusDays(1); - persistResource( - Registry.get("eap") - .asBuilder() - .setEapFeeSchedule( - ImmutableSortedMap.of( - START_OF_TIME, Money.of(USD, 0), - a, Money.of(USD, 100), - b, Money.of(USD, 50))) - .build()); - - basicCreateCost = - PricingEngineProxy.getPricesForDomainName("example.tld", clock.nowUtc()).getCreateCost(); - } - - @Test - public void testTldSpecificLogicEngine() { - TldSpecificLogicProxy.EppCommandOperations createPrice = - TldSpecificLogicProxy.getCreatePrice( - Registry.get("tld"), "example.tld", clock.nowUtc(), 1); - assertThat(createPrice.getTotalCost()).isEqualTo(basicCreateCost); - assertThat(createPrice.getFees()).hasSize(1); - } - - @Test - public void testTldSpecificLogicEngineEap() { - TldSpecificLogicProxy.EppCommandOperations createPrice = - TldSpecificLogicProxy.getCreatePrice( - Registry.get("eap"), "example.eap", clock.nowUtc(), 1); - assertThat(createPrice.getTotalCost()).isEqualTo(basicCreateCost.plus(Money.of(USD, 100))); - assertThat(createPrice.getCurrency()).isEqualTo(USD); - assertThat(createPrice.getFees().get(0)) - .isEqualTo(Fee.create(basicCreateCost.getAmount(), FeeType.CREATE)); - assertThat(createPrice.getFees().get(1)) - .isEqualTo( - Fee.create( - Money.of(USD, 100).getAmount(), FeeType.EAP, clock.nowUtc().plusDays(1))); - assertThat(createPrice.getFees()) - .isEqualTo( - ImmutableList.of( - Fee.create(basicCreateCost.getAmount(), FeeType.CREATE), - Fee.create( - Money.of(USD, 100).getAmount(), - FeeType.EAP, - clock.nowUtc().plusDays(1)))); - } -}