google-nomulus/java/google/registry/model/registry/Registry.java
cgoldfeder ae039aa0d8 Remove all vestiges of memcache
Memcache is already off but now it's not in the code anymore.

This includes removing domain creation failfast, since that is actually
slower now than just running the flow - all you gain is a non-transactional
read over a transactional read, but the cost is that you always pay that
read, which is going to drive up latency.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=158183506
2017-06-14 10:28:24 -04:00

960 lines
36 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.registry;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.joda.money.CurrencyUnit.USD;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Range;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Work;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.annotation.OnSave;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.ReservationType;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.registry.label.ReservedList.ReservedListEntry;
import google.registry.util.Idn;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Interval;
/** Persisted per-TLD configuration data. */
@ReportedOn
@Entity
public class Registry extends ImmutableObject implements Buildable {
@Parent
Key<EntityGroupRoot> parent = getCrossTldKey();
/**
* The canonical string representation of the TLD associated with this {@link Registry}, which
* is the standard ASCII for regular TLDs and punycoded ASCII for IDN TLDs.
*/
@Id
String tldStrId;
/**
* A duplicate of {@link #tldStrId}, to simplify BigQuery reporting since the id field becomes
* {@code __key__.name} rather than being exported as a named field.
*/
String tldStr;
/**
* The suffix that identifies roids as belonging to this specific tld, e.g. -HOW for .how.
*/
String roidSuffix;
/** Default values for all the relevant TLD parameters. */
public static final TldState DEFAULT_TLD_STATE = TldState.PREDELEGATION;
public static final boolean DEFAULT_ESCROW_ENABLED = false;
public static final boolean DEFAULT_DNS_PAUSED = false;
public static final Duration DEFAULT_ADD_GRACE_PERIOD = Duration.standardDays(5);
public static final Duration DEFAULT_SUNRUSH_ADD_GRACE_PERIOD = Duration.standardDays(30);
public static final Duration DEFAULT_AUTO_RENEW_GRACE_PERIOD = Duration.standardDays(45);
public static final Duration DEFAULT_REDEMPTION_GRACE_PERIOD = Duration.standardDays(30);
public static final Duration DEFAULT_RENEW_GRACE_PERIOD = Duration.standardDays(5);
public static final Duration DEFAULT_TRANSFER_GRACE_PERIOD = Duration.standardDays(5);
public static final Duration DEFAULT_AUTOMATIC_TRANSFER_LENGTH = Duration.standardDays(5);
public static final Duration DEFAULT_PENDING_DELETE_LENGTH = Duration.standardDays(5);
public static final Duration DEFAULT_ANCHOR_TENANT_ADD_GRACE_PERIOD = Duration.standardDays(30);
public static final CurrencyUnit DEFAULT_CURRENCY = USD;
public static final Money DEFAULT_CREATE_BILLING_COST = Money.of(USD, 8);
public static final Money DEFAULT_EAP_BILLING_COST = Money.of(USD, 0);
public static final Money DEFAULT_RENEW_BILLING_COST = Money.of(USD, 8);
public static final Money DEFAULT_RESTORE_BILLING_COST = Money.of(USD, 100);
public static final Money DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST = Money.of(USD, 20);
/** The type of TLD, which determines things like backups and escrow policy. */
public enum TldType {
/** A real, official TLD. */
REAL,
/** A test TLD, for the prober. */
TEST
}
/**
* The states a TLD can be in at any given point in time. The ordering below is the required
* sequence of states (ignoring {@link #PDT} which is a pseudo-state).
*/
public enum TldState {
/** The state of not yet being delegated to this registry in the root zone by IANA. */
PREDELEGATION,
/**
* The state in which only trademark holders can submit applications for domains. Doing so
* requires a claims notice to be submitted with the application.
* */
SUNRISE,
/**
* The state representing the overlap of {@link #SUNRISE} with a "landrush" state in which
* anyone can submit an application for a domain name. Sunrise applications may continue
* during landrush, so we model the overlap as a distinct state named "sunrush".
*/
SUNRUSH,
/**
* The state in which anyone can submit an application for a domain name. Sunrise applications
* are not allowed during this phase.
*/
LANDRUSH,
/**
* A state in which no domain operations are permitted. Generally used after sunrise or
* landrush to allocate uncontended applications and send contended applications to auction.
* This state is special in that it has no ordering constraints and can appear after any phase.
*/
QUIET_PERIOD,
/**
* The steady state of a TLD in which all domain names are available via first-come,
* first-serve.
*/
GENERAL_AVAILABILITY,
/** A "fake" state for use in predelegation testing. Acts like {@link #GENERAL_AVAILABILITY}. */
PDT
}
/**
* A transition to a TLD state at a specific time, for use in a TimedTransitionProperty.
* Public because AppEngine's security manager requires this for instantiation via reflection.
*/
@Embed
public static class TldStateTransition extends TimedTransition<TldState> {
/** The TLD state. */
private TldState tldState;
@Override
public TldState getValue() {
return tldState;
}
@Override
protected void setValue(TldState tldState) {
this.tldState = tldState;
}
}
/**
* A transition to a given billing cost at a specific time, for use in a TimedTransitionProperty.
* Public because AppEngine's security manager requires this for instantiation via reflection.
*/
@Embed
public static class BillingCostTransition extends TimedTransition<Money> {
/** The billing cost value. */
private Money billingCost;
@Override
public Money getValue() {
return billingCost;
}
@Override
protected void setValue(Money billingCost) {
this.billingCost = billingCost;
}
}
/** Returns the registry for a given TLD, throwing if none exists. */
public static Registry get(String tld) {
Registry registry = CACHE.getUnchecked(tld).orNull();
if (registry == null) {
throw new RegistryNotFoundException(tld);
}
return registry;
}
/**
* Invalidates the cache entry.
*
* <p>This is called automatically when the registry is saved. One should also call it when a
* registry is deleted.
*/
@OnSave
public void invalidateInCache() {
CACHE.invalidate(tldStr);
}
/** A cache that loads the {@link Registry} for a given tld. */
private static final LoadingCache<String, Optional<Registry>> CACHE =
CacheBuilder.newBuilder()
.expireAfterWrite(getSingletonCacheRefreshDuration().getMillis(), MILLISECONDS)
.build(new CacheLoader<String, Optional<Registry>>() {
@Override
public Optional<Registry> load(final String tld) {
// Enter a transactionless context briefly; we don't want to enroll every TLD in a
// transaction that might be wrapping this call.
return Optional.fromNullable(ofy().doTransactionless(new Work<Registry>() {
@Override
public Registry run() {
return ofy()
.load()
.key(Key.create(getCrossTldKey(), Registry.class, tld))
.now();
}}));
}});
/**
* The name of the pricing engine that this TLD uses.
*
* <p>This must be a valid key for the map of pricing engines injected by
* {@code @Inject Map<String, PricingEngine>}.
*
* <p>Note that it used to be the canonical class name, hence the name of this field, but this
* restriction has since been relaxed and it may now be any unique string.
*/
String pricingEngineClassName;
/**
* The name of the DnsWriter that this TLD uses.
*
* <p>This must be a valid key for the map of DnsWriters injected by <code>
* @Inject Map<String, DnsWriter></code>
*/
String dnsWriter;
/**
* The unicode-aware representation of the TLD associated with this {@link Registry}.
*
* <p>This will be equal to {@link #tldStr} for ASCII TLDs, but will be non-ASCII for IDN TLDs.
* We store this in a field so that it will be retained upon import into BigQuery.
*/
String tldUnicode;
/**
* Id of the folder in drive used to public (export) information for this TLD.
*
* <p>This is optional; if not configured, then information won't be exported for this TLD.
*/
String driveFolderId;
/** The type of the TLD, whether it's real or for testing. */
TldType tldType = TldType.REAL;
/**
* A property that transitions to different TldStates at different times. Stored as a list of
* TldStateTransition embedded objects using the @Mapify annotation.
*/
@Mapify(TimedTransitionProperty.TimeMapper.class)
TimedTransitionProperty<TldState, TldStateTransition> tldStateTransitions =
TimedTransitionProperty.forMapify(DEFAULT_TLD_STATE, TldStateTransition.class);
/** An automatically managed creation timestamp. */
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
/** The set of reserved lists that are applicable to this registry. */
Set<Key<ReservedList>> reservedLists;
/** Retrieves an ImmutableSet of all ReservedLists associated with this tld. */
public ImmutableSet<Key<ReservedList>> getReservedLists() {
return nullToEmptyImmutableCopy(reservedLists);
}
/** The static {@link PremiumList} for this TLD, if there is one. */
Key<PremiumList> premiumList;
/** Should RDE upload a nightly escrow deposit for this TLD? */
boolean escrowEnabled = DEFAULT_ESCROW_ENABLED;
/** Whether the pull queue that writes to authoritative DNS is paused for this TLD. */
boolean dnsPaused = DEFAULT_DNS_PAUSED;
/** Whether the price must be acknowledged to register premiun names on this TLD. */
boolean premiumPriceAckRequired = true;
/**
* Whether only domains with {@link ReservationType#NAMESERVER_RESTRICTED} reservation type in a
* reserved list can be registered on this TLD.
*/
boolean domainCreateRestricted;
/** The length of the add grace period for this TLD. */
Duration addGracePeriodLength = DEFAULT_ADD_GRACE_PERIOD;
/** The length of the add grace period for this TLD. */
Duration anchorTenantAddGracePeriodLength = DEFAULT_ANCHOR_TENANT_ADD_GRACE_PERIOD;
/** The length of the sunrush add grace period for this TLD. */
Duration sunrushAddGracePeriodLength = DEFAULT_SUNRUSH_ADD_GRACE_PERIOD;
/** The length of the auto renew grace period for this TLD. */
Duration autoRenewGracePeriodLength = DEFAULT_AUTO_RENEW_GRACE_PERIOD;
/** The length of the redemption grace period for this TLD. */
Duration redemptionGracePeriodLength = DEFAULT_REDEMPTION_GRACE_PERIOD;
/** The length of the renew grace period for this TLD. */
Duration renewGracePeriodLength = DEFAULT_RENEW_GRACE_PERIOD;
/** The length of the transfer grace period for this TLD. */
Duration transferGracePeriodLength = DEFAULT_TRANSFER_GRACE_PERIOD;
/** The length of time before a transfer is automatically approved for this TLD. */
Duration automaticTransferLength = DEFAULT_AUTOMATIC_TRANSFER_LENGTH;
/** The length of time a domain spends in the non-redeemable pending delete phase for this TLD. */
Duration pendingDeleteLength = DEFAULT_PENDING_DELETE_LENGTH;
/** The currency unit for all costs associated with this TLD. */
CurrencyUnit currency = DEFAULT_CURRENCY;
/** The per-year billing cost for registering a new domain name. */
Money createBillingCost = DEFAULT_CREATE_BILLING_COST;
/** The one-time billing cost for restoring a domain name from the redemption grace period. */
Money restoreBillingCost = DEFAULT_RESTORE_BILLING_COST;
/** The one-time billing cost for changing the server status (i.e. lock). */
Money serverStatusChangeBillingCost = DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST;
/**
* A property that transitions to different renew billing costs at different times. Stored as a
* list of BillingCostTransition embedded objects using the @Mapify annotation.
*
* <p>A given value of this property represents the per-year billing cost for renewing a domain
* name. This cost is also used to compute costs for transfers, since each transfer includes a
* renewal to ensure transfers have a cost.
*/
@Mapify(TimedTransitionProperty.TimeMapper.class)
TimedTransitionProperty<Money, BillingCostTransition> renewBillingCostTransitions =
TimedTransitionProperty.forMapify(DEFAULT_RENEW_BILLING_COST, BillingCostTransition.class);
/**
* A property that tracks the EAP fee schedule (if any) for the TLD.
*/
@Mapify(TimedTransitionProperty.TimeMapper.class)
TimedTransitionProperty<Money, BillingCostTransition> eapFeeSchedule =
TimedTransitionProperty.forMapify(DEFAULT_EAP_BILLING_COST, BillingCostTransition.class);
String lordnUsername;
/** The end of the claims period (at or after this time, claims no longer applies). */
DateTime claimsPeriodEnd = END_OF_TIME;
/**
* The (inclusive) start {@link DateTime} of LRP. This (and lrpPeriodEnd) exist for serialization
* purposes, though everything else that interacts with the LRP period should use getLrpPeriod()
* and setLrpPeriod(), which uses an {@link Interval}.
*/
DateTime lrpPeriodStart;
/**
* The (exclusive) end {@link DateTime} of LRP. This (and lrpPeriodStart) exist for serialization
* purposes, though everything else that interacts with the LRP period should use getLrpPeriod()
* and setLrpPeriod(), which uses an {@link Interval}.
*/
DateTime lrpPeriodEnd;
/** A whitelist of clients allowed to be used on domains on this TLD (ignored if empty). */
Set<String> allowedRegistrantContactIds;
/** A whitelist of hosts allowed to be used on domains on this TLD (ignored if empty). */
Set<String> allowedFullyQualifiedHostNames;
public String getTldStr() {
return tldStr;
}
public String getRoidSuffix() {
return roidSuffix;
}
/** Retrieve the actual domain name representing the TLD for which this registry operates. */
public InternetDomainName getTld() {
return InternetDomainName.from(tldStr);
}
/** Retrieve the TLD type (real or test). */
public TldType getTldType() {
return tldType;
}
/**
* Retrieve the TLD state at the given time. Defaults to {@link TldState#PREDELEGATION}.
*
* <p>Note that {@link TldState#PDT} TLDs pretend to be in {@link TldState#GENERAL_AVAILABILITY}.
*/
public TldState getTldState(DateTime now) {
TldState state = tldStateTransitions.getValueAtTime(now);
return TldState.PDT.equals(state) ? TldState.GENERAL_AVAILABILITY : state;
}
/** Retrieve whether this TLD is in predelegation testing. */
public boolean isPdt(DateTime now) {
return TldState.PDT.equals(tldStateTransitions.getValueAtTime(now));
}
public DateTime getCreationTime() {
return creationTime.getTimestamp();
}
public boolean getEscrowEnabled() {
return escrowEnabled;
}
public boolean getDnsPaused() {
return dnsPaused;
}
public String getDriveFolderId() {
return driveFolderId;
}
public boolean getPremiumPriceAckRequired() {
return premiumPriceAckRequired;
}
/**
* Returns true if only domains with nameserver restricted reservation on this TLD can be created.
*/
public boolean getDomainCreateRestricted() {
return domainCreateRestricted;
}
public Duration getAddGracePeriodLength() {
return addGracePeriodLength;
}
public Duration getSunrushAddGracePeriodLength() {
return sunrushAddGracePeriodLength;
}
public Duration getAutoRenewGracePeriodLength() {
return autoRenewGracePeriodLength;
}
public Duration getRedemptionGracePeriodLength() {
return redemptionGracePeriodLength;
}
public Duration getRenewGracePeriodLength() {
return renewGracePeriodLength;
}
public Duration getTransferGracePeriodLength() {
return transferGracePeriodLength;
}
public Duration getAutomaticTransferLength() {
return automaticTransferLength;
}
public Duration getPendingDeleteLength() {
return pendingDeleteLength;
}
public Duration getAnchorTenantAddGracePeriodLength() {
return anchorTenantAddGracePeriodLength;
}
@Nullable
public Key<PremiumList> getPremiumList() {
return premiumList;
}
public CurrencyUnit getCurrency() {
return currency;
}
/**
* Use <code>PricingEngineProxy.getDomainCreateCost</code> instead of this to find the cost for a
* domain create.
*/
@VisibleForTesting
public Money getStandardCreateCost() {
return createBillingCost;
}
/**
* Returns the add-on cost of a domain restore (the flat registry-wide fee charged in addition to
* one year of renewal for that name).
*/
public Money getStandardRestoreCost() {
return restoreBillingCost;
}
/**
* Use <code>PricingEngineProxy.getDomainRenewCost</code> instead of this to find the cost for a
* domain renewal, and all derived costs (i.e. autorenews, transfers, and the per-domain part of a
* restore cost).
*/
@VisibleForTesting
public Money getStandardRenewCost(DateTime now) {
return renewBillingCostTransitions.getValueAtTime(now);
}
/**
* Returns the cost of a server status change (i.e. lock).
*/
public Money getServerStatusChangeCost() {
return serverStatusChangeBillingCost;
}
public ImmutableSortedMap<DateTime, TldState> getTldStateTransitions() {
return tldStateTransitions.toValueMap();
}
public ImmutableSortedMap<DateTime, Money> getRenewBillingCostTransitions() {
return renewBillingCostTransitions.toValueMap();
}
/**
* Returns the EAP fee for the registry at the given time.
*/
public Fee getEapFeeFor(DateTime now) {
ImmutableSortedMap<DateTime, Money> valueMap = eapFeeSchedule.toValueMap();
DateTime periodStart = valueMap.floorKey(now);
DateTime periodEnd = valueMap.ceilingKey(now);
// NOTE: assuming END_OF_TIME would never be reached...
Range<DateTime> validPeriod = Range.closedOpen(
periodStart != null ? periodStart : START_OF_TIME,
periodEnd != null ? periodEnd : END_OF_TIME);
return Fee.create(
eapFeeSchedule.getValueAtTime(now).getAmount(),
FeeType.EAP,
validPeriod,
validPeriod.upperEndpoint());
}
public String getLordnUsername() {
return lordnUsername;
}
public DateTime getClaimsPeriodEnd() {
return claimsPeriodEnd;
}
public String getPremiumPricingEngineClassName() {
return pricingEngineClassName;
}
public String getDnsWriter() {
return dnsWriter;
}
public ImmutableSet<String> getAllowedRegistrantContactIds() {
return nullToEmptyImmutableCopy(allowedRegistrantContactIds);
}
public ImmutableSet<String> getAllowedFullyQualifiedHostNames() {
return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames);
}
public Interval getLrpPeriod() {
return (lrpPeriodStart == null && lrpPeriodEnd == null)
? new Interval(START_OF_TIME, Duration.ZERO) // An empty duration.
: new Interval(lrpPeriodStart, lrpPeriodEnd);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link Registry} objects, since they are immutable. */
public static class Builder extends Buildable.Builder<Registry> {
public Builder() {}
private Builder(Registry instance) {
super(instance);
}
public Builder setTldType(TldType tldType) {
getInstance().tldType = tldType;
return this;
}
/** Sets the TLD state to transition to the specified states at the specified times. */
public Builder setTldStateTransitions(
ImmutableSortedMap<DateTime, TldState> tldStatesMap) {
checkNotNull(tldStatesMap, "TLD states map cannot be null");
// Filter out any entries with QUIET_PERIOD as the value before checking for ordering, since
// that phase is allowed to appear anywhere.
checkArgument(Ordering.natural().isStrictlyOrdered(
Iterables.filter(tldStatesMap.values(), not(equalTo(TldState.QUIET_PERIOD)))),
"The TLD states are chronologically out of order");
getInstance().tldStateTransitions =
TimedTransitionProperty.fromValueMap(tldStatesMap, TldStateTransition.class);
return this;
}
public Builder setTldStr(String tldStr) {
checkArgument(tldStr != null, "TLD must not be null");
getInstance().tldStr = tldStr;
return this;
}
public Builder setEscrowEnabled(boolean enabled) {
getInstance().escrowEnabled = enabled;
return this;
}
public Builder setDnsPaused(boolean paused) {
getInstance().dnsPaused = paused;
return this;
}
public Builder setDriveFolderId(String driveFolderId) {
getInstance().driveFolderId = driveFolderId;
return this;
}
public Builder setPremiumPriceAckRequired(boolean premiumPriceAckRequired) {
getInstance().premiumPriceAckRequired = premiumPriceAckRequired;
return this;
}
public Builder setDomainCreateRestricted(boolean domainCreateRestricted) {
getInstance().domainCreateRestricted = domainCreateRestricted;
return this;
}
public Builder setPremiumPricingEngine(String pricingEngineClass) {
getInstance().pricingEngineClassName = checkArgumentNotNull(pricingEngineClass);
return this;
}
public Builder setDnsWriter(String dnsWriter) {
getInstance().dnsWriter = checkArgumentNotNull(dnsWriter);
return this;
}
public Builder setAddGracePeriodLength(Duration addGracePeriodLength) {
checkArgument(addGracePeriodLength.isLongerThan(Duration.ZERO),
"addGracePeriodLength must be non-zero");
getInstance().addGracePeriodLength = addGracePeriodLength;
return this;
}
public Builder setSunrushAddGracePeriodLength(Duration sunrushAddGracePeriodLength) {
checkArgument(sunrushAddGracePeriodLength.isLongerThan(Duration.ZERO),
"sunrushAddGracePeriodLength must be non-zero");
getInstance().sunrushAddGracePeriodLength = sunrushAddGracePeriodLength;
return this;
}
/** Warning! Changing this will affect the billing time of autorenew events in the past. */
public Builder setAutoRenewGracePeriodLength(Duration autoRenewGracePeriodLength) {
checkArgument(autoRenewGracePeriodLength.isLongerThan(Duration.ZERO),
"autoRenewGracePeriodLength must be non-zero");
getInstance().autoRenewGracePeriodLength = autoRenewGracePeriodLength;
return this;
}
public Builder setRedemptionGracePeriodLength(Duration redemptionGracePeriodLength) {
checkArgument(redemptionGracePeriodLength.isLongerThan(Duration.ZERO),
"redemptionGracePeriodLength must be non-zero");
getInstance().redemptionGracePeriodLength = redemptionGracePeriodLength;
return this;
}
public Builder setRenewGracePeriodLength(Duration renewGracePeriodLength) {
checkArgument(renewGracePeriodLength.isLongerThan(Duration.ZERO),
"renewGracePeriodLength must be non-zero");
getInstance().renewGracePeriodLength = renewGracePeriodLength;
return this;
}
public Builder setTransferGracePeriodLength(Duration transferGracePeriodLength) {
checkArgument(transferGracePeriodLength.isLongerThan(Duration.ZERO),
"transferGracePeriodLength must be non-zero");
getInstance().transferGracePeriodLength = transferGracePeriodLength;
return this;
}
public Builder setAutomaticTransferLength(Duration automaticTransferLength) {
checkArgument(automaticTransferLength.isLongerThan(Duration.ZERO),
"automaticTransferLength must be non-zero");
getInstance().automaticTransferLength = automaticTransferLength;
return this;
}
public Builder setPendingDeleteLength(Duration pendingDeleteLength) {
checkArgument(pendingDeleteLength.isLongerThan(Duration.ZERO),
"pendingDeleteLength must be non-zero");
getInstance().pendingDeleteLength = pendingDeleteLength;
return this;
}
public Builder setCurrency(CurrencyUnit currency) {
checkArgument(currency != null, "currency must be non-null");
getInstance().currency = currency;
return this;
}
public Builder setCreateBillingCost(Money amount) {
checkArgument(amount.isPositiveOrZero(), "createBillingCost cannot be negative");
getInstance().createBillingCost = amount;
return this;
}
public Builder setReservedListsByName(Set<String> reservedListNames) {
checkArgument(reservedListNames != null, "reservedListNames must not be null");
ImmutableSet.Builder<ReservedList> builder = new ImmutableSet.Builder<>();
for (String reservedListName : reservedListNames) {
// Check for existence of the reserved list and throw an exception if it doesn't exist.
Optional<ReservedList> reservedList = ReservedList.get(reservedListName);
checkArgument(
reservedList.isPresent(),
"Could not find reserved list %s to add to the tld",
reservedListName);
builder.add(reservedList.get());
}
return setReservedLists(builder.build());
}
public Builder setReservedLists(ReservedList... reservedLists) {
return setReservedLists(ImmutableSet.copyOf(reservedLists));
}
public Builder setReservedLists(Set<ReservedList> reservedLists) {
checkArgumentNotNull(reservedLists, "reservedLists must not be null");
checkAuthCodeConflicts(reservedLists);
ImmutableSet.Builder<Key<ReservedList>> builder = new ImmutableSet.Builder<>();
for (ReservedList reservedList : reservedLists) {
builder.add(Key.create(reservedList));
}
getInstance().reservedLists = builder.build();
return this;
}
/**
* Checks that domain names don't have conflicting auth codes across different reserved lists.
*/
private static void checkAuthCodeConflicts(Set<ReservedList> reservedLists) {
Multimap<String, String> allAuthCodes = LinkedHashMultimap.create();
for (ReservedList list : reservedLists) {
for (ReservedListEntry entry : list.getReservedListEntries().values()) {
if (entry.getAuthCode() != null) {
allAuthCodes.put(entry.getLabel(), entry.getAuthCode());
}
}
}
ImmutableSet<Entry<String, Collection<String>>> conflicts =
FluentIterable.from(allAuthCodes.asMap().entrySet())
.filter(new Predicate<Entry<String, Collection<String>>>() {
@Override
public boolean apply(Entry<String, Collection<String>> entry) {
return entry.getValue().size() > 1;
}})
.toSet();
checkArgument(
conflicts.isEmpty(),
"Cannot set reserved lists because of auth code conflicts for labels: %s",
conflicts);
}
public Builder setPremiumList(PremiumList premiumList) {
getInstance().premiumList = (premiumList == null) ? null : Key.create(premiumList);
return this;
}
public Builder setRestoreBillingCost(Money amount) {
checkArgument(amount.isPositiveOrZero(), "restoreBillingCost cannot be negative");
getInstance().restoreBillingCost = amount;
return this;
}
/**
* Sets the renew billing cost to transition to the specified values at the specified times.
*
* <p>Renew billing costs transitions should only be added at least 5 days (the length of an
* automatic transfer) in advance, to avoid discrepancies between the cost stored with the
* billing event (created when the transfer is requested) and the cost at the time when the
* transfer actually occurs (5 days later).
*/
public Builder setRenewBillingCostTransitions(
ImmutableSortedMap<DateTime, Money> renewCostsMap) {
checkArgumentNotNull(renewCostsMap, "Renew billing costs map cannot be null");
checkArgument(Iterables.all(
renewCostsMap.values(),
new Predicate<Money>() {
@Override
public boolean apply(Money amount) {
return amount.isPositiveOrZero();
}}),
"Renew billing cost cannot be negative");
getInstance().renewBillingCostTransitions =
TimedTransitionProperty.fromValueMap(renewCostsMap, BillingCostTransition.class);
return this;
}
/**
* Sets the EAP fee schedule for the TLD.
*/
public Builder setEapFeeSchedule(
ImmutableSortedMap<DateTime, Money> eapFeeSchedule) {
checkArgumentNotNull(eapFeeSchedule, "EAP schedule map cannot be null");
checkArgument(Iterables.all(
eapFeeSchedule.values(),
new Predicate<Money>() {
@Override
public boolean apply(Money amount) {
return amount.isPositiveOrZero();
}}),
"EAP fee cannot be negative");
getInstance().eapFeeSchedule =
TimedTransitionProperty.fromValueMap(eapFeeSchedule, BillingCostTransition.class);
return this;
}
public Builder setRoidSuffix(String roidSuffix) {
getInstance().roidSuffix = roidSuffix;
return this;
}
public Builder setServerStatusChangeBillingCost(Money amount) {
checkArgument(
amount.isPositiveOrZero(), "Server status change billing cost cannot be negative");
getInstance().serverStatusChangeBillingCost = amount;
return this;
}
public Builder setLordnUsername(String username) {
getInstance().lordnUsername = username;
return this;
}
public Builder setClaimsPeriodEnd(DateTime claimsPeriodEnd) {
getInstance().claimsPeriodEnd = checkArgumentNotNull(claimsPeriodEnd);
return this;
}
public Builder setAllowedRegistrantContactIds(
ImmutableSet<String> allowedRegistrantContactIds) {
getInstance().allowedRegistrantContactIds = allowedRegistrantContactIds;
return this;
}
public Builder setAllowedFullyQualifiedHostNames(
ImmutableSet<String> allowedFullyQualifiedHostNames) {
getInstance().allowedFullyQualifiedHostNames = allowedFullyQualifiedHostNames;
return this;
}
public Builder setLrpPeriod(@Nullable Interval lrpPeriod) {
getInstance().lrpPeriodStart = (lrpPeriod == null ? null : lrpPeriod.getStart());
getInstance().lrpPeriodEnd = (lrpPeriod == null ? null : lrpPeriod.getEnd());
return this;
}
@Override
public Registry build() {
final Registry instance = getInstance();
// Pick up the name of the associated TLD from the instance object.
String tldName = instance.tldStr;
checkArgument(tldName != null, "No registry TLD specified");
// Check for canonical form by converting to an InternetDomainName and then back.
checkArgument(
InternetDomainName.isValid(tldName)
&& tldName.equals(InternetDomainName.from(tldName).toString()),
"Cannot create registry for TLD that is not a valid, canonical domain name");
// Check the validity of all TimedTransitionProperties to ensure that they have values for
// START_OF_TIME. The setters above have already checked this for new values, but also check
// here to catch cases where we loaded an invalid TimedTransitionProperty from Datastore and
// cloned it into a new builder, to block re-building a Registry in an invalid state.
instance.tldStateTransitions.checkValidity();
instance.renewBillingCostTransitions.checkValidity();
instance.eapFeeSchedule.checkValidity();
// All costs must be in the expected currency.
// TODO(b/21854155): When we move PremiumList into Datastore, verify its currency too.
checkArgument(
instance.getStandardCreateCost().getCurrencyUnit().equals(instance.currency),
"Create cost must be in the registry's currency");
checkArgument(
instance.getStandardRestoreCost().getCurrencyUnit().equals(instance.currency),
"Restore cost must be in the registry's currency");
checkArgument(
instance.getServerStatusChangeCost().getCurrencyUnit().equals(instance.currency),
"Server status change cost must be in the registry's currency");
Predicate<Money> currencyCheck = new Predicate<Money>(){
@Override
public boolean apply(Money money) {
return money.getCurrencyUnit().equals(instance.currency);
}};
checkArgument(
Iterables.all(
instance.getRenewBillingCostTransitions().values(), currencyCheck),
"Renew cost must be in the registry's currency");
checkArgument(
Iterables.all(
instance.eapFeeSchedule.toValueMap().values(), currencyCheck),
"All EAP fees must be in the registry's currency");
checkArgumentNotNull(
instance.pricingEngineClassName, "All registries must have a configured pricing engine");
checkArgumentNotNull(
instance.dnsWriter,
"A DNS writer must be specified. VoidDnsWriter can be used if DNS writing isn't wanted");
instance.tldStrId = tldName;
instance.tldUnicode = Idn.toUnicode(tldName);
return super.build();
}
}
/** Exception to throw when no Registry is found for a given tld. */
public static class RegistryNotFoundException extends RuntimeException{
RegistryNotFoundException(String tld) {
super("No registry object found for " + tld);
}
}
}