Add extra logic for all relevant flows

This CL enhances various domain flows (check, create, delete, renew, restore, transfer, update) so that they invoke the appropriate methods on the object implementing the TLD's RegistryExtraFlowLogic (if any). TldSpecificLogicProxy is also updated to invoke RegistryExtraFlowLogic proxy (if any) to fetch the appropriate price. The tests use a made-up extra flow logic object which can be attached to a test TLD to make sure that the proper routines are being invoked.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=132486734
This commit is contained in:
mountford 2016-09-07 15:23:50 -07:00 committed by Ben McIlwain
parent a6db24c8bb
commit 95cc7ab3d8
46 changed files with 1173 additions and 394 deletions

View file

@ -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");
}
}
}

View file

@ -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() {}

View file

@ -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> 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 {

View file

@ -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<R extends DomainBase, B extends Build
protected TldState tldState;
protected Optional<LrpToken> lrpToken;
protected Optional<RegistryExtraFlowLogic> 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<R extends DomainBase, B extends Build
Registry registry = Registry.get(tld);
tldState = registry.getTldState(now);
checkRegistryStateForTld(tld);
// Now that the TLD has been verified, we can go ahead and initialize extraFlowLogic. The
// initialization and matching commit are done at the topmost possible level in the flow
// hierarchy, but the actual processing takes place only when needed in the children, e.g.
// DomainCreateFlow.
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForTld(tld);
domainLabel = domainName.parts().get(0);
commandOperations = TldSpecificLogicProxy.getCreatePrice(
registry, domainName.toString(), now, command.getPeriod().getValue());
registry,
domainName.toString(),
getClientId(),
now,
command.getPeriod().getValue(),
eppInput);
// The TLD should always be the parent of the requested domain name.
isAnchorTenantViaReservation = matchesAnchorTenantReservation(
domainLabel, tld, command.getAuthInfo().getPw().getValue());
@ -252,6 +266,9 @@ public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Build
.setRedemptionHistoryEntry(Key.create(historyEntry))
.build());
}
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().commitAdditionalLogicChanges();
}
}
/** Validate the secDNS extension, if present. */

View file

@ -58,12 +58,7 @@ public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Build
public final void initResourceCreateOrMutateFlow() throws EppException {
command = cloneAndLinkReferences(command, now);
initDomainUpdateFlow();
// In certain conditions (for instance, errors), there is no existing resource.
if (existingResource == null) {
extraFlowLogic = Optional.absent();
} else {
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld());
}
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
}
@SuppressWarnings("unused")
@ -143,6 +138,18 @@ public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Build
validateNameserversCountForTld(newResource.getTld(), newResource.getNameservers().size());
}
/** Call the subclass method, then commit any extra flow logic. */
@Override
protected final void modifyRelatedResources() {
modifyUpdateRelatedResources();
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().commitAdditionalLogicChanges();
}
}
/** Modify any other resources that need to be informed of this update. */
protected void modifyUpdateRelatedResources() {}
/** The secDNS:all element must have value 'true' if present. */
static class SecDnsAllUsageException extends ParameterValuePolicyErrorException {
public SecDnsAllUsageException() {

View file

@ -16,7 +16,6 @@ package google.registry.flows.domain;
import static google.registry.flows.domain.DomainFlowUtils.DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER;
import static google.registry.model.eppoutput.Result.Code.Success;
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
@ -118,7 +117,6 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainAppl
@Override
protected void initDomainCreateFlow() {
registerExtensions(LaunchCreateExtension.class);
registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
}
@Override
@ -215,6 +213,7 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainAppl
responseExtensionsBuilder.add(feeCreate.createResponseBuilder()
.setCurrency(commandOperations.getCurrency())
.setFees(commandOperations.getFees())
.setCredits(commandOperations.getCredits())
.build());
}

View file

@ -171,8 +171,10 @@ public class DomainCheckFlow extends BaseDomainCheckFlow {
builder,
domainName,
getTldFromDomainName(domainName),
getClientId(),
topLevelCurrency,
now);
now,
eppInput);
feeCheckResponseItemsBuilder
.add(builder.setDomainNameIfSupported(domainName).build());
}

View file

@ -16,11 +16,9 @@ package google.registry.flows.domain;
import static com.google.common.collect.Sets.union;
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER;
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.flows.EppException;
@ -127,7 +125,6 @@ public class DomainCreateFlow extends DomainCreateOrAllocateFlow {
@Override
protected final void initDomainCreateOrAllocateFlow() {
registerExtensions(LaunchCreateExtension.class);
registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
}
@Override
@ -190,6 +187,16 @@ public class DomainCreateFlow extends DomainCreateOrAllocateFlow {
.setLaunchNotice(launchCreate.getNotice())
.setSmdId(signedMark == null ? null : signedMark.getId());
}
// Handle extra flow logic, if any. The initialization and commit are performed higher up in the
// flow hierarchy, in BaseDomainCreateFlow.
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().performAdditionalDomainCreateLogic(
existingResource,
getClientId(),
now,
command.getPeriod().getValue(),
eppInput);
}
}
@Override

View file

@ -110,6 +110,7 @@ public abstract class DomainCreateOrAllocateFlow
feeCreate.createResponseBuilder()
.setCurrency(commandOperations.getCurrency())
.setFees(commandOperations.getFees())
.setCredits(commandOperations.getCredits())
.build()));
}
}

View file

@ -23,6 +23,7 @@ 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;
@ -36,6 +37,7 @@ import google.registry.model.domain.DomainCommand.Delete;
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;
@ -76,11 +78,14 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
ImmutableList<Credit> credits;
protected Optional<RegistryExtraFlowLogic> 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<DomainResource, Bui
}
@Override
protected final void setDeleteProperties(Builder builder) {
protected final void setDeleteProperties(Builder builder) throws EppException {
// Only set to PENDING_DELETE if this domain is not in the Add Grace Period. If domain is in Add
// Grace Period, we delete it immediately.
// The base class code already handles the immediate delete case, so we only have to handle the
@ -122,6 +127,12 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
getClientId())))
.setDeletePollMessage(Key.create(deletePollMessage));
}
// Handle extra flow logic, if any.
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().performAdditionalDomainDeleteLogic(
existingResource, getClientId(), now, eppInput);
}
}
@Override
@ -151,8 +162,7 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
ofy().load().key(checkNotNull(gracePeriod.getOneTimeBillingEvent())).now().getCost();
}
creditsBuilder.add(Credit.create(
cost.negated().getAmount(),
String.format("%s credit", gracePeriod.getType().getXmlName())));
cost.negated().getAmount(), FeeType.CREDIT, gracePeriod.getType().getXmlName()));
creditsCurrencyUnit = cost.getCurrencyUnit();
}
}
@ -165,6 +175,10 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
// Close the autorenew billing event and poll message. This may delete the poll message.
updateAutorenewRecurrenceEndTime(existingResource, now);
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().commitAdditionalLogicChanges();
}
// 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.

View file

@ -61,6 +61,7 @@ import google.registry.model.domain.DomainCommand.CreateOrUpdate;
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
@ -71,6 +72,7 @@ import google.registry.model.domain.launch.LaunchExtension;
import google.registry.model.domain.launch.LaunchPhase;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import google.registry.model.host.HostResource;
import google.registry.model.mark.Mark;
@ -86,7 +88,6 @@ import google.registry.model.smd.AbstractSignedMark;
import google.registry.model.smd.EncodedSignedMark;
import google.registry.model.smd.SignedMark;
import google.registry.model.smd.SignedMarkRevocationList;
import google.registry.pricing.TldSpecificLogicProxy;
import google.registry.tmch.TmchXmlSignature;
import google.registry.tmch.TmchXmlSignature.CertificateSignatureException;
import google.registry.util.Idn;
@ -565,8 +566,10 @@ public class DomainFlowUtils {
FeeQueryResponseExtensionItem.Builder builder,
String domainName,
String tld,
String clientIdentifier,
@Nullable CurrencyUnit topLevelCurrency,
DateTime now) throws EppException {
DateTime now,
EppInput eppInput) throws EppException {
InternetDomainName domain = InternetDomainName.from(domainName);
Registry registry = Registry.get(tld);
int years = verifyUnitIsYears(feeRequest.getPeriod()).getValue();
@ -589,8 +592,6 @@ public class DomainFlowUtils {
.setClass(TldSpecificLogicProxy.getFeeClass(domainName, now).orNull());
switch (feeRequest.getCommandName()) {
case UNKNOWN:
throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
case CREATE:
if (isReserved(domain, isSunrise)) { // Don't return a create price for reserved names.
builder.setClass("reserved"); // Override whatever class we've set above.
@ -598,24 +599,35 @@ public class DomainFlowUtils {
builder.setReasonIfSupported("reserved");
} else {
builder.setAvailIfSupported(true);
builder.setFees(
TldSpecificLogicProxy.getCreatePrice(registry, domainName, now, years).getFees());
builder.setFees(TldSpecificLogicProxy.getCreatePrice(
registry, domainName, clientIdentifier, now, years, eppInput).getFees());
}
break;
case RENEW:
builder.setAvailIfSupported(true);
builder.setFees(TldSpecificLogicProxy.getRenewPrice(
registry, domainName, clientIdentifier, now, years, eppInput).getFees());
break;
case RESTORE:
if (years != 1) {
throw new RestoresAreAlwaysForOneYearException();
}
builder.setAvailIfSupported(true);
builder.setFees(
TldSpecificLogicProxy.getRestorePrice(registry, domainName, now, years).getFees());
builder.setFees(TldSpecificLogicProxy.getRestorePrice(
registry, domainName, clientIdentifier, now, eppInput).getFees());
break;
// TODO(mountford): handle UPDATE
default:
// Anything else (transfer|renew) will have a "renew" fee.
case TRANSFER:
builder.setAvailIfSupported(true);
builder.setFees(
TldSpecificLogicProxy.getRenewPrice(registry, domainName, now, years).getFees());
builder.setFees(TldSpecificLogicProxy.getTransferPrice(
registry, domainName, clientIdentifier, now, years, eppInput).getFees());
break;
case UPDATE:
builder.setAvailIfSupported(true);
builder.setFees(TldSpecificLogicProxy.getUpdatePrice(
registry, domainName, clientIdentifier, now, eppInput).getFees());
break;
default:
throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
}
}
@ -646,6 +658,12 @@ public class DomainFlowUtils {
}
total = total.add(fee.getCost());
}
for (Credit credit : feeCommand.getCredits()) {
if (!credit.hasDefaultAttributes()) {
throw new UnsupportedFeeAttributeException();
}
total = total.add(credit.getCost());
}
Money feeTotal = null;
try {

View file

@ -99,13 +99,15 @@ public class DomainInfoFlow extends BaseDomainInfoFlow<DomainResource, Builder>
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<RegistryExtraFlowLogic> extraLogicManager =
RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld());
RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
if (extraLogicManager.isPresent()) {
List<String> flags = extraLogicManager.get().getExtensionFlags(
existingResource, this.getClientId(), now); // As-of date is always now for info commands.

View file

@ -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<DomainResource, Ren
protected FeeTransformCommandExtension feeRenew;
protected Money renewCost;
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
@Inject DomainRenewFlow() {}
@Override
@ -96,6 +99,7 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
registerExtensions(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
feeRenew =
eppInput.getFirstExtensionOfClasses(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
}
@Override
@ -117,7 +121,7 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
}
@Override
protected DomainResource createOrMutateResource() {
protected DomainResource createOrMutateResource() throws EppException {
DateTime newExpirationTime = leapSafeAddYears(
existingResource.getRegistrationExpirationTime(), command.getPeriod().getValue());
// Bill for this explicit renew itself.
@ -143,6 +147,13 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
.setEventTime(newExpirationTime)
.setParent(historyEntry)
.build();
// Handle extra flow logic, if any.
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().performAdditionalDomainRenewLogic(
existingResource, getClientId(), now, command.getPeriod().getValue(), eppInput);
}
ofy().save().<Object>entities(explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
return existingResource.asBuilder()
.setRegistrationExpirationTime(newExpirationTime)
@ -160,6 +171,14 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
}
}
/** Commit any extra flow logic. */
@Override
protected final void modifyRelatedResources() {
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().commitAdditionalLogicChanges();
}
}
@Override
protected final HistoryEntry.Type getHistoryEntryType() {
return HistoryEntry.Type.DOMAIN_RENEW;

View file

@ -26,6 +26,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
@ -75,6 +76,7 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
protected FeeTransformCommandExtension feeUpdate;
protected Money restoreCost;
protected Money renewCost;
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
@Inject DomainRestoreRequestFlow() {}
@ -82,6 +84,7 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
protected final void initResourceCreateOrMutateFlow() throws EppException {
registerExtensions(RgpUpdateExtension.class);
registerExtensions(FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
}
@Override
@ -155,6 +158,13 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
.build();
ofy().save().<Object>entities(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<DomainReso
// Update the relevant {@link ForeignKey} to cache the new deletion time.
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
ofy().delete().key(existingResource.getDeletePollMessage());
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().commitAdditionalLogicChanges();
}
}
@Override

View file

@ -25,6 +25,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
@ -39,6 +40,7 @@ import google.registry.model.domain.Period;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransformCommandExtension;
import google.registry.model.domain.flags.FlagsTransferCommandExtension;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.model.poll.PollMessage;
import google.registry.model.registry.Registry;
@ -87,6 +89,9 @@ public class DomainTransferRequestFlow
/** The amount that this transfer will cost due to the implied renew. */
private Money renewCost;
/** Extra flow logic instance. */
protected Optional<RegistryExtraFlowLogic> 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

View file

@ -138,7 +138,7 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow<DomainResource, Build
}
@Override
protected final void modifyRelatedResources() {
protected final void modifyUpdateRelatedResources() {
// Determine the status changes, and filter to server statuses.
// If any of these statuses have been added or removed, bill once.
if (metadataExtension != null && metadataExtension.getRequestedByRegistrar()) {
@ -161,10 +161,6 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow<DomainResource, Build
ofy().save().entity(billingEvent);
}
}
if (extraFlowLogic.isPresent()) {
extraFlowLogic.get().commitAdditionalDomainUpdates();
}
}
@Override

View file

@ -16,6 +16,7 @@ package google.registry.flows.domain;
import google.registry.flows.EppException;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.fee.BaseFee;
import google.registry.model.eppinput.EppInput;
import java.util.List;
import org.joda.time.DateTime;
@ -26,20 +27,96 @@ import org.joda.time.DateTime;
*/
public interface RegistryExtraFlowLogic {
/** Get the flags to be used in the EPP flags extension. This is used for EPP info commands. */
/** Gets the flags to be used in the EPP flags extension. This is used for EPP info commands. */
public List<String> 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();
}

View file

@ -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<RegistryExtraFlowLogic> newInstanceForTld(String tld) {
public static <D extends DomainBase> Optional<RegistryExtraFlowLogic>
newInstanceForDomain(@Nullable D domain) throws EppException {
if (domain == null) {
return Optional.absent();
} else {
return newInstanceForTld(domain.getTld());
}
}
public static Optional<RegistryExtraFlowLogic>
newInstanceForTld(String tld) throws EppException {
if (extraLogicOverrideMap.containsKey(tld)) {
try {
return Optional.<RegistryExtraFlowLogic>of(extraLogicOverrideMap.get(tld).newInstance());
} catch (InstantiationException | IllegalAccessException e) {
return Optional.absent();
return Optional.<RegistryExtraFlowLogic>of(
extraLogicOverrideMap.get(tld).getConstructor().newInstance());
} catch (ReflectiveOperationException ex) {
throw new CommandFailedException();
}
}
return Optional.absent();

View file

@ -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<Fee> fees;
private final ImmutableList<Credit> credits;
/** Constructs an EppCommandOperations object using separate lists of fees and credits. */
EppCommandOperations(
CurrencyUnit currency, ImmutableList<Fee> fees, ImmutableList<Credit> 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<Fee> feeBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<Credit> 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<Fee> getFees() {
return fees;
}
/** Returns the list of credits for the event. */
public List<Credit> 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<RegistryExtraFlowLogic> 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<RegistryExtraFlowLogic> 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<RegistryExtraFlowLogic> 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<String> 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.
*
* <p>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<LrpToken> 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.<LrpToken>absent();
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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<Credit> getCredits() {
return credits;
return nullToEmpty(credits);
}
}

View file

@ -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<Fee> fees);
Builder setCredits(ImmutableList<Credit> credits);
Builder setFees(List<Fee> fees);
Builder setCredits(List<Credit> credits);
FeeTransformResponseExtension build();
}
}

View file

@ -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<Fee> fees) {
public B setFees(List<Fee> fees) {
getInstance().fees = fees;
return thisCastToDerived();
}
@Override
public B setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
public B setCredits(List<Credit> credits) {
getInstance().credits = forceEmptyToNull(credits);
return thisCastToDerived();
}
}

View file

@ -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<Fee> fees) {
public B setFees(List<Fee> fees) {
getInstance().fees = fees;
return thisCastToDerived();
}
@Override
public B setCredits(ImmutableList<Credit> credits) {
public B setCredits(List<Credit> credits) {
return thisCastToDerived();
}
}

View file

@ -30,4 +30,8 @@ import javax.xml.bind.annotation.XmlRootElement;
public class FlagsCreateCommandExtension implements CommandExtension {
@XmlElement(name = "flag")
List<String> flags;
public List<String> getFlags() {
return flags;
}
}

View file

@ -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;
}
}

View file

@ -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<Fee> fees;
EppCommandOperations(CurrencyUnit currency, ImmutableList<Fee> 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<Fee> 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<Fee> 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<String> 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.
*
* <p>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<LrpToken> 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.<LrpToken>absent();
}
}

View file

@ -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 ResourceFlowTestCase<DomainCreateFlow,
@Before
public void initCreateTest() throws Exception {
createTld("tld");
createTlds("tld", "flags");
persistResource(Registry.get("tld").asBuilder()
.setReservedLists(persistReservedList(
"tld-reserved",
@ -142,6 +144,7 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow,
"anchor,RESERVED_FOR_ANCHOR_TENANT,2fooBAR"))
.build());
persistClaimsList(ImmutableMap.of("example-one", CLAIMS_KEY));
RegistryExtraFlowLogicProxy.setOverride("flags", TestExtraLogicManager.class);
}
/**
@ -1604,4 +1607,20 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow,
.build());
doSuccessfulTest("tld", "domain_create_response.xml");
}
@Test
public void testFailure_flags_feeMismatch() throws Exception {
persistContactsAndHosts();
setEppInput("domain_create_flags.xml", ImmutableMap.of("FEE", "12"));
thrown.expect(FeesMismatchException.class);
runFlow();
}
@Test
public void testSuccess_flags() throws Exception {
persistContactsAndHosts();
setEppInput("domain_create_flags.xml", ImmutableMap.of("FEE", "42"));
thrown.expect(IllegalArgumentException.class, "flag1,flag2");
runFlow();
}
}

View file

@ -19,7 +19,7 @@ import static google.registry.flows.domain.DomainTransferFlowTestCase.persistWit
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.ofy.ObjectifyService.ofy;
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.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.getOnlyPollMessage;
import static google.registry.testing.DatastoreHelper.getPollMessages;
@ -56,6 +56,7 @@ import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.TestExtraLogicManager;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import google.registry.model.eppcommon.StatusValue;
@ -100,7 +101,9 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
@Before
public void initDomainTest() throws Exception {
createTld("tld");
createTlds("tld", "flags");
// For flags extension tests.
RegistryExtraFlowLogicProxy.setOverride("flags", TestExtraLogicManager.class);
}
private void setupSuccessfulTest() throws Exception {
@ -696,4 +699,12 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
persistResource(newDomainResource(getUniqueIdFromCommand()));
runFlow();
}
@Test
public void testSuccess_flags() throws Exception {
setEppInput("domain_delete_flags.xml");
setupSuccessfulTest();
thrown.expect(IllegalArgumentException.class, "deleted");
runFlow();
}
}

View file

@ -18,6 +18,7 @@ import static google.registry.flows.domain.DomainTransferFlowTestCase.persistWit
import static google.registry.model.ofy.ObjectifyService.ofy;
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.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
@ -53,6 +54,7 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.TestExtraLogicManager;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.poll.PollMessage;
@ -85,7 +87,9 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
@Before
public void initDomainTest() {
createTld("tld");
createTlds("tld", "flags");
// For flags extension tests.
RegistryExtraFlowLogicProxy.setOverride("flags", TestExtraLogicManager.class);
}
private void persistDomain(StatusValue... statusValues) throws Exception {
@ -607,4 +611,12 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
persistDomain();
runFlow();
}
@Test
public void testSuccess_flags() throws Exception {
setEppInput("domain_renew_flags.xml");
persistDomain();
thrown.expect(IllegalArgumentException.class, "renewed");
runFlow();
}
}

View file

@ -18,10 +18,12 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
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.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.getPollMessages;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistDeletedDomain;
import static google.registry.testing.DatastoreHelper.persistReservedList;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.DomainResourceSubject.assertAboutDomains;
@ -54,6 +56,7 @@ import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.TestExtraLogicManager;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.poll.PollMessage;
@ -82,7 +85,9 @@ public class DomainRestoreRequestFlowTest extends
@Before
public void initDomainTest() {
createTld("tld");
createTlds("tld", "flags");
// For flags extension tests.
RegistryExtraFlowLogicProxy.setOverride("flags", TestExtraLogicManager.class);
}
void persistPendingDeleteDomain() throws Exception {
@ -446,6 +451,13 @@ public class DomainRestoreRequestFlowTest extends
runFlow();
}
@Test
public void testFailure_fullyDeleted() throws Exception {
thrown.expect(ResourceToMutateDoesNotExistException.class);
persistDeletedDomain(getUniqueIdFromCommand(), clock.nowUtc());
runFlow();
}
@Test
public void testFailure_withChange() throws Exception {
thrown.expect(RestoreCommandIncludesChangesException.class);
@ -539,4 +551,12 @@ public class DomainRestoreRequestFlowTest extends
persistPendingDeleteDomain();
runFlow();
}
@Test
public void testSuccess_flags() throws Exception {
setEppInput("domain_update_restore_request_flags.xml");
persistPendingDeleteDomain();
thrown.expect(IllegalArgumentException.class, "restored");
runFlow();
}
}

View file

@ -17,6 +17,7 @@ package google.registry.flows.domain;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.deleteResource;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.getOnlyPollMessage;
@ -61,6 +62,7 @@ import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.TestExtraLogicManager;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
@ -95,6 +97,8 @@ public class DomainTransferRequestFlowTest
setEppInput("domain_transfer_request.xml");
setClientIdForFlow("NewRegistrar");
setupDomain("tld");
createTld("flags");
RegistryExtraFlowLogicProxy.setOverride("flags", TestExtraLogicManager.class);
}
private void assertTransferRequested(EppResource resource) throws Exception {
@ -760,4 +764,13 @@ public class DomainTransferRequestFlowTest
domain.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build());
doFailingTest("domain_transfer_request.xml");
}
@Test
public void testSuccess_flags() throws Exception {
setEppInput("domain_transfer_request_flags.xml");
setupDomain("example", "flags");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
thrown.expect(IllegalArgumentException.class, "add:flag1,flag2;remove:flag3,flag4");
runFlow();
}
}

View file

@ -1185,7 +1185,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
setEppInput("domain_update_addremove_flags.xml");
persistReferencedEntities();
persistDomain();
thrown.expect(UnimplementedExtensionException.class);
thrown.expect(IllegalArgumentException.class, "add:flag1,flag2;remove:flag3,flag4");
runFlow();
}
}

View file

@ -0,0 +1,221 @@
// 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.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.createTlds;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
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.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.googlecode.objectify.Key;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.TestExtraLogicManager;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.ofy.Ofy;
import google.registry.model.poll.PollMessage;
import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import google.registry.pricing.PricingEngineProxy;
import google.registry.testing.AppEngineRule;
import google.registry.testing.ExceptionRule;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule;
import google.registry.testing.ShardableTestCase;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
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 extends ShardableTestCase {
@Rule
public final InjectRule inject = new InjectRule();
@Rule
public final ExceptionRule thrown = new ExceptionRule();
@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);
createTlds("tld", "eap", "test");
RegistryExtraFlowLogicProxy.setOverride("test", TestExtraLogicManager.class);
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 test_basicCreatePrice() throws Exception {
TldSpecificLogicProxy.EppCommandOperations createPrice =
TldSpecificLogicProxy.getCreatePrice(
Registry.get("tld"), "example.tld", "clientIdentifer", clock.nowUtc(), 1, null);
assertThat(createPrice.getTotalCost()).isEqualTo(basicCreateCost);
assertThat(createPrice.getFees()).hasSize(1);
}
@Test
public void test_eap() throws Exception {
TldSpecificLogicProxy.EppCommandOperations createPrice =
TldSpecificLogicProxy.getCreatePrice(
Registry.get("eap"), "example.eap", "clientIdentifier", clock.nowUtc(), 1, null);
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())
.containsExactly(
Fee.create(basicCreateCost.getAmount(), FeeType.CREATE),
Fee.create(Money.of(USD, 100).getAmount(), FeeType.EAP, clock.nowUtc().plusDays(1)))
.inOrder();
}
@Test
public void test_extraLogic_createPrice() throws Exception {
TldSpecificLogicProxy.EppCommandOperations price =
TldSpecificLogicProxy.getCreatePrice(
Registry.get("test"), "create-54.test", "clientIdentifier", clock.nowUtc(), 3, null);
assertThat(price.getCurrency()).isEqualTo(CurrencyUnit.USD);
assertThat(price.getFees()).hasSize(1);
assertThat(price.getFees().get(0).getCost()).isEqualTo(new BigDecimal(54));
assertThat(price.getFees().get(0).getDescription()).isEqualTo("create");
assertThat(price.getCredits()).isEmpty();
}
@Test
public void test_extraLogic_renewPrice() throws Exception {
persistActiveDomain("renew--13.test");
TldSpecificLogicProxy.EppCommandOperations price =
TldSpecificLogicProxy.getRenewPrice(
Registry.get("test"), "renew--13.test", "clientIdentifier", clock.nowUtc(), 1, null);
assertThat(price.getCurrency()).isEqualTo(CurrencyUnit.USD);
assertThat(price.getFees()).isEmpty();
assertThat(price.getCredits()).hasSize(1);
assertThat(price.getCredits().get(0).getCost()).isEqualTo(new BigDecimal(-13));
assertThat(price.getCredits().get(0).getDescription()).isEqualTo("renew");
}
@Test
public void test_extraLogic_renewPrice_noDomain() throws Exception {
thrown.expect(ResourceToMutateDoesNotExistException.class);
TldSpecificLogicProxy.getRenewPrice(
Registry.get("test"), "renew--13.test", "clientIdentifier", clock.nowUtc(), 1, null);
}
void persistPendingDeleteDomain(String domainName, DateTime now) throws Exception {
DomainResource domain = newDomainResource(domainName);
HistoryEntry historyEntry = persistResource(
new HistoryEntry.Builder()
.setType(HistoryEntry.Type.DOMAIN_DELETE)
.setParent(domain)
.build());
domain = persistResource(domain.asBuilder()
.setRegistrationExpirationTime(now.plusYears(5).plusDays(45))
.setDeletionTime(now.plusDays(35))
.addGracePeriod(GracePeriod.create(
GracePeriodStatus.REDEMPTION, now.plusDays(1), "foo", null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.setDeletePollMessage(Key.create(persistResource(
new PollMessage.OneTime.Builder()
.setClientId("TheRegistrar")
.setEventTime(now.plusDays(5))
.setParent(historyEntry)
.build())))
.build());
clock.advanceOneMilli();
}
@Test
public void test_extraLogic_restorePrice() throws Exception {
persistPendingDeleteDomain("renew-13.test", clock.nowUtc());
TldSpecificLogicProxy.EppCommandOperations price =
TldSpecificLogicProxy.getRestorePrice(
Registry.get("test"), "renew-13.test", "clientIdentifier", clock.nowUtc(), null);
assertThat(price.getCurrency()).isEqualTo(CurrencyUnit.USD);
assertThat(price.getFees()).hasSize(2);
assertThat(price.getFees().get(0).getCost()).isEqualTo(new BigDecimal(13));
assertThat(price.getFees().get(0).getDescription()).isEqualTo("renew");
assertThat(price.getFees().get(1).getDescription()).isEqualTo("restore");
assertThat(price.getCredits()).isEmpty();
}
@Test
public void test_extraLogic_restorePrice_noDomain() throws Exception {
thrown.expect(ResourceToMutateDoesNotExistException.class);
TldSpecificLogicProxy.getRestorePrice(
Registry.get("test"), "renew-13.test", "clientIdentifier", clock.nowUtc(), null);
}
@Test
public void test_extraLogic_transferPrice() throws Exception {
persistActiveDomain("renew-26.test");
TldSpecificLogicProxy.EppCommandOperations price =
TldSpecificLogicProxy.getTransferPrice(
Registry.get("test"), "renew-26.test", "clientIdentifier", clock.nowUtc(), 2, null);
assertThat(price.getCurrency()).isEqualTo(CurrencyUnit.USD);
assertThat(price.getFees()).hasSize(1);
assertThat(price.getFees().get(0).getCost()).isEqualTo(new BigDecimal(26));
assertThat(price.getFees().get(0).getDescription()).isEqualTo("renew");
assertThat(price.getCredits()).isEmpty();
}
@Test
public void test_extraLogic_updatePrice() throws Exception {
persistActiveDomain("update-13.test");
TldSpecificLogicProxy.EppCommandOperations price =
TldSpecificLogicProxy.getUpdatePrice(
Registry.get("test"), "update-13.test", "clientIdentifier", clock.nowUtc(), null);
assertThat(price.getCurrency()).isEqualTo(CurrencyUnit.USD);
assertThat(price.getFees()).hasSize(1);
assertThat(price.getFees().get(0).getCost()).isEqualTo(new BigDecimal(13));
assertThat(price.getFees().get(0).getDescription()).isEqualTo("update");
assertThat(price.getCredits()).isEmpty();
}
}

View file

@ -46,7 +46,7 @@
<fee:currency>USD</fee:currency>
<fee:command>update</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
<fee:fee description="update">0.00</fee:fee>
</fee:cd>
</fee:chkData>
</extension>

View file

@ -56,7 +56,7 @@
</fee:object>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
<fee:fee description="update">0.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>

View file

@ -20,7 +20,7 @@
<fee:command>update</fee:command>
<fee:currency>USD</fee:currency>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">100.00</fee:fee>
<fee:fee description="update">0.00</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
</fee:chkData>

View file

@ -60,7 +60,7 @@
</fee:object>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">100.00</fee:fee>
<fee:fee description="update">0.00</fee:fee>
<fee:class>premium</fee:class>
</fee:command>
</fee:cd>

View file

@ -0,0 +1,32 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>create-42.flags</domain:name>
<domain:period unit="y">2</domain:period>
<domain:ns>
<domain:hostObj>ns1.example.net</domain:hostObj>
<domain:hostObj>ns2.example.net</domain:hostObj>
</domain:ns>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:create>
</create>
<extension>
<fee:create xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:currency>USD</fee:currency>
<fee:fee>%FEE%</fee:fee>
</fee:create>
<flags:create xmlns:flags="urn:google:params:xml:ns:flags-0.1">
<flags:flag>flag1</flags:flag>
<flags:flag>flag2</flags:flag>
</flags:create>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,11 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.flags</domain:name>
</domain:delete>
</delete>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,13 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<renew>
<domain:renew
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.flags</domain:name>
<domain:curExpDate>2000-04-03</domain:curExpDate>
<domain:period unit="y">5</domain:period>
</domain:renew>
</renew>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,27 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<transfer op="request">
<domain:transfer
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.flags</domain:name>
<domain:period unit="y">1</domain:period>
<domain:authInfo>
<domain:pw roid="JD1234-REP">2fooBAR</domain:pw>
</domain:authInfo>
</domain:transfer>
</transfer>
<extension>
<flags:transfer xmlns:flags="urn:google:params:xml:ns:flags-0.1">
<flags:add>
<flags:flag>flag1</flags:flag>
<flags:flag>flag2</flags:flag>
</flags:add>
<flags:rem>
<flags:flag>flag3</flags:flag>
<flags:flag>flag4</flags:flag>
</flags:rem>
</flags:transfer>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,17 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.flags</domain:name>
<domain:chg/>
</domain:update>
</update>
<extension>
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:restore op="request"/>
</rgp:update>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -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<String> 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<String> 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);
}
}

View file

@ -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))));
}
}