mirror of
https://github.com/google/nomulus.git
synced 2025-07-23 11:16:04 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
120
java/google/registry/model/registry/Registries.java
Normal file
120
java/google/registry/model/registry/Registries.java
Normal file
|
@ -0,0 +1,120 @@
|
|||
// 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 com.google.domain.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.Strings.emptyToNull;
|
||||
import static com.google.common.collect.Maps.filterValues;
|
||||
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CacheUtils.memoizeWithShortExpiration;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.model.registry.Registry.TldType;
|
||||
|
||||
import com.googlecode.objectify.Work;
|
||||
|
||||
/** Utilities for finding and listing {@link Registry} entities. */
|
||||
public final class Registries {
|
||||
|
||||
private Registries() {}
|
||||
|
||||
/** Supplier of a cached registries map. */
|
||||
private static Supplier<ImmutableMap<String, TldType>> cache = createFreshCache();
|
||||
|
||||
/**
|
||||
* Returns a newly-created Supplier of a registries to types map.
|
||||
*
|
||||
* <p>The supplier's get() method enters a transactionless context briefly to avoid enrolling the
|
||||
* query inside an unrelated client-affecting transaction.
|
||||
*/
|
||||
private static Supplier<ImmutableMap<String, TldType>> createFreshCache() {
|
||||
return memoizeWithShortExpiration(new Supplier<ImmutableMap<String, TldType>>() {
|
||||
@Override
|
||||
public ImmutableMap<String, TldType> get() {
|
||||
return ofy().doTransactionless(new Work<ImmutableMap<String, TldType>>() {
|
||||
@Override
|
||||
public ImmutableMap<String, TldType> run() {
|
||||
ImmutableMap.Builder<String, TldType> builder = new ImmutableMap.Builder<>();
|
||||
for (Registry registry : ofy().load().type(Registry.class).ancestor(getCrossTldKey())) {
|
||||
builder.put(registry.getTldStr(), registry.getTldType());
|
||||
}
|
||||
return builder.build();
|
||||
}});
|
||||
}});
|
||||
}
|
||||
|
||||
/** Manually reset the static cache backing the methods on this class. */
|
||||
// TODO(b/24903801): offer explicit cached and uncached paths instead.
|
||||
public static void resetCache() {
|
||||
cache = createFreshCache();
|
||||
}
|
||||
|
||||
public static ImmutableSet<String> getTlds() {
|
||||
return cache.get().keySet();
|
||||
}
|
||||
|
||||
public static ImmutableSet<String> getTldsOfType(TldType type) {
|
||||
return ImmutableSet.copyOf(filterValues(cache.get(), equalTo(type)).keySet());
|
||||
}
|
||||
|
||||
/** Shortcut to check whether a tld exists or else throw. If it exists, it is returned back. */
|
||||
public static String assertTldExists(String tld) {
|
||||
checkArgument(
|
||||
getTlds().contains(checkNotNull(emptyToNull(tld), "Null or empty TLD specified")),
|
||||
"TLD %s does not exist",
|
||||
tld);
|
||||
return tld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TLD which the domain name or hostname falls under, no matter how many levels of
|
||||
* sublabels there are.
|
||||
*
|
||||
* <p><b>Note:</b> This routine will only work on names under TLDs for which this registry is
|
||||
* authoritative. To extract TLDs from domains (not hosts) that other registries control, use
|
||||
* {@link com.google.domain.registry.util.DomainNameUtils#getTldFromDomainName(String)
|
||||
* DomainNameUtils#getTldFromDomainName}.
|
||||
*
|
||||
* @param domainName domain name or host name (but not TLD) under an authoritative TLD
|
||||
* @return TLD or absent if {@code domainName} has no labels under an authoritative TLD
|
||||
*/
|
||||
public static Optional<InternetDomainName> findTldForName(InternetDomainName domainName) {
|
||||
ImmutableSet<String> tlds = getTlds();
|
||||
while (domainName.hasParent()) {
|
||||
domainName = domainName.parent();
|
||||
if (tlds.contains(domainName.toString())) {
|
||||
return Optional.of(domainName);
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered TLD which this domain name falls under, or throws an exception if no
|
||||
* match exists.
|
||||
*/
|
||||
public static InternetDomainName findTldForNameOrThrow(InternetDomainName domainName) {
|
||||
return checkNotNull(
|
||||
findTldForName(domainName).orNull(),
|
||||
"Domain name is not under a recognized TLD: %s", domainName.toString());
|
||||
}
|
||||
}
|
813
java/google/registry/model/registry/Registry.java
Normal file
813
java/google/registry/model/registry/Registry.java
Normal file
|
@ -0,0 +1,813 @@
|
|||
// 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 com.google.domain.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 com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
|
||||
import static com.google.domain.registry.model.registry.label.PremiumList.getPremiumPrice;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static com.google.domain.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.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.model.Buildable;
|
||||
import com.google.domain.registry.model.CreateAutoTimestamp;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
import com.google.domain.registry.model.common.EntityGroupRoot;
|
||||
import com.google.domain.registry.model.common.TimedTransitionProperty;
|
||||
import com.google.domain.registry.model.common.TimedTransitionProperty.TimedTransition;
|
||||
import com.google.domain.registry.model.registry.label.PremiumList;
|
||||
import com.google.domain.registry.model.registry.label.ReservedList;
|
||||
import com.google.domain.registry.util.Idn;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Work;
|
||||
import com.googlecode.objectify.annotation.Cache;
|
||||
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 org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** Persisted per-TLD configuration data. */
|
||||
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
|
||||
@Entity
|
||||
public class Registry extends ImmutableObject implements Buildable {
|
||||
|
||||
@Parent
|
||||
Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
|
||||
@Id
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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_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 SLDs 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;
|
||||
}
|
||||
}
|
||||
|
||||
/** A cache that loads the {@link Registry} for a given tld. */
|
||||
private static final LoadingCache<String, Optional<Registry>> CACHE = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(
|
||||
RegistryEnvironment.get().config().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, and memcached results are fine here.
|
||||
return Optional.fromNullable(ofy().doTransactionless(new Work<Registry>() {
|
||||
@Override
|
||||
public Registry run() {
|
||||
return ofy()
|
||||
.load()
|
||||
.key(Key.create(getCrossTldKey(), Registry.class, tld))
|
||||
.now();
|
||||
}}));
|
||||
}});
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
/** Whenever a registry is saved, invalidate the cache entry. */
|
||||
@OnSave
|
||||
void updateCache() {
|
||||
CACHE.invalidate(tldStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 publish information 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(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 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 {@link PremiumList} for this TLD. */
|
||||
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;
|
||||
|
||||
/** 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(
|
||||
ImmutableSortedMap.of(START_OF_TIME, DEFAULT_RENEW_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;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Key<PremiumList> getPremiumList() {
|
||||
return premiumList;
|
||||
}
|
||||
|
||||
public CurrencyUnit getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
/** Use {@link #getDomainCreateCost} 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 {@link #getDomainRenewCost} instead of this to find the cost for a domain renew, 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();
|
||||
}
|
||||
|
||||
private Optional<Money> getPremiumPriceForSld(String sldName) {
|
||||
return getPremiumPriceForSld(InternetDomainName.from(sldName));
|
||||
}
|
||||
|
||||
private Optional<Money> getPremiumPriceForSld(InternetDomainName domainName) {
|
||||
checkArgument(getTld().equals(domainName.parent()),
|
||||
"Domain name %s is not an SLD for TLD %s", domainName.toString(), tldStr);
|
||||
String label = domainName.parts().get(0);
|
||||
return getPremiumPrice(label, tldStr);
|
||||
}
|
||||
|
||||
/** Returns true if the given domain name is on the premium price list. */
|
||||
public boolean isPremiumName(String domainName) {
|
||||
return isPremiumName(InternetDomainName.from(domainName));
|
||||
}
|
||||
|
||||
/** Returns true if the given domain name is on the premium price list. */
|
||||
public boolean isPremiumName(InternetDomainName domainName) {
|
||||
return getPremiumPriceForSld(domainName).isPresent();
|
||||
}
|
||||
|
||||
/** Returns the billing cost for registering the specified domain name for this many years. */
|
||||
public Money getDomainCreateCost(String domainName, int years) {
|
||||
checkArgument(years > 0, "Number of years must be positive");
|
||||
Money annualCost = getPremiumPriceForSld(domainName).or(getStandardCreateCost());
|
||||
return annualCost.multipliedBy(years);
|
||||
}
|
||||
|
||||
/** Returns the billing cost for renewing the specified domain name for this many years. */
|
||||
public Money getDomainRenewCost(String domainName, int years, DateTime now) {
|
||||
checkArgument(years > 0, "Number of years must be positive");
|
||||
Money annualCost = getPremiumPriceForSld(domainName).or(getStandardRenewCost(now));
|
||||
return annualCost.multipliedBy(years);
|
||||
}
|
||||
|
||||
public String getLordnUsername() {
|
||||
return lordnUsername;
|
||||
}
|
||||
|
||||
public DateTime getClaimsPeriodEnd() {
|
||||
return claimsPeriodEnd;
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getAllowedRegistrantContactIds() {
|
||||
return nullToEmptyImmutableCopy(allowedRegistrantContactIds);
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getAllowedFullyQualifiedHostNames() {
|
||||
return nullToEmptyImmutableCopy(allowedFullyQualifiedHostNames);
|
||||
}
|
||||
|
||||
@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 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(), "create billing cost cannot be negative");
|
||||
getInstance().createBillingCost = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReservedListsByName(Set<String> reservedListNames) {
|
||||
checkArgument(reservedListNames != null, "reservedListNames must not be null");
|
||||
ImmutableSet.Builder<Key<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);
|
||||
if (!reservedList.isPresent()) {
|
||||
throw new IllegalStateException(
|
||||
"Could not find reserved list " + reservedListName + " to add to the tld");
|
||||
}
|
||||
builder.add(Key.create(reservedList.get()));
|
||||
}
|
||||
getInstance().reservedLists = builder.build();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReservedLists(ReservedList... reservedLists) {
|
||||
return setReservedLists(ImmutableSet.copyOf(reservedLists));
|
||||
}
|
||||
|
||||
public Builder setReservedLists(Set<ReservedList> reservedLists) {
|
||||
checkArgumentNotNull(reservedLists, "reservedLists must not be null");
|
||||
ImmutableSet.Builder<Key<ReservedList>> builder = new ImmutableSet.Builder<>();
|
||||
for (ReservedList reservedList : reservedLists) {
|
||||
builder.add(Key.create(reservedList));
|
||||
}
|
||||
getInstance().reservedLists = builder.build();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPremiumList(PremiumList premiumList) {
|
||||
getInstance().premiumList = (premiumList == null) ? null : Key.create(premiumList);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRestoreBillingCost(Money amount) {
|
||||
checkArgument(amount.isPositiveOrZero(), "restore billing cost 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) {
|
||||
checkNotNull(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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@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();
|
||||
// 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");
|
||||
checkArgument(
|
||||
Iterables.all(
|
||||
instance.getRenewBillingCostTransitions().values(),
|
||||
new Predicate<Money>(){
|
||||
@Override
|
||||
public boolean apply(Money money) {
|
||||
return money.getCurrencyUnit().equals(instance.currency);
|
||||
}}),
|
||||
"Renew cost must be in the registry's currency");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
92
java/google/registry/model/registry/RegistryCursor.java
Normal file
92
java/google/registry/model/registry/RegistryCursor.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
// 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 com.google.domain.registry.model.registry;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Shared entity type for per-TLD date cursors. */
|
||||
@Entity
|
||||
public class RegistryCursor extends ImmutableObject {
|
||||
|
||||
/** The types of cursors, used as the string id field for each cursor in datastore. */
|
||||
public enum CursorType {
|
||||
/** Cursor for ensuring rolling transactional isolation of BRDA staging operation. */
|
||||
BRDA,
|
||||
|
||||
/** Cursor for ensuring rolling transactional isolation of RDE report operation. */
|
||||
RDE_REPORT,
|
||||
|
||||
/** Cursor for ensuring rolling transactional isolation of RDE staging operation. */
|
||||
RDE_STAGING,
|
||||
|
||||
/** Cursor for ensuring rolling transactional isolation of RDE upload operation. */
|
||||
RDE_UPLOAD,
|
||||
|
||||
/**
|
||||
* Cursor that tracks the last time we talked to the escrow provider's SFTP server for a given
|
||||
* TLD.
|
||||
*
|
||||
* <p>Our escrow provider has an odd feature where separate deposits uploaded within two hours
|
||||
* of each other will be merged into a single deposit. This is problematic in situations where
|
||||
* the cursor might be a few days behind and is trying to catch up.
|
||||
*
|
||||
* <p>The way we solve this problem is by having {@code RdeUploadAction} check this cursor
|
||||
* before performing an upload for a given TLD. If the cursor is less than two hours old, the
|
||||
* action will fail with a status code above 300 and App Engine will keep retrying the action
|
||||
* until it's ready.
|
||||
*/
|
||||
RDE_UPLOAD_SFTP;
|
||||
}
|
||||
|
||||
@Parent
|
||||
Key<Registry> registry;
|
||||
|
||||
@Id
|
||||
String cursorType;
|
||||
|
||||
DateTime date;
|
||||
|
||||
/** Convenience shortcut to load a cursor for a given registry and cursor type. */
|
||||
public static Optional<DateTime> load(Registry registry, CursorType cursorType) {
|
||||
Key<RegistryCursor> key =
|
||||
Key.create(Key.create(registry), RegistryCursor.class, cursorType.name());
|
||||
RegistryCursor cursor = ofy().load().key(key).now();
|
||||
return Optional.fromNullable(cursor == null ? null : cursor.date);
|
||||
}
|
||||
|
||||
/** Convenience shortcut to save a cursor. */
|
||||
public static void save(Registry registry, CursorType cursorType, DateTime value) {
|
||||
ofy().save().entity(create(registry, cursorType, value));
|
||||
}
|
||||
|
||||
/** Creates a new cursor instance. */
|
||||
public static RegistryCursor create(Registry registry, CursorType cursorType, DateTime date) {
|
||||
RegistryCursor instance = new RegistryCursor();
|
||||
instance.registry = Key.create(registry);
|
||||
instance.cursorType = cursorType.name();
|
||||
instance.date = date;
|
||||
return instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
// 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 com.google.domain.registry.model.registry.label;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
import static com.google.domain.registry.model.registry.Registries.getTlds;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.domain.registry.model.Buildable;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
import com.google.domain.registry.model.common.EntityGroupRoot;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.label.ReservedList.ReservedListEntry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Base class for {@link ReservedList} and {@link PremiumList} objects stored in Datastore.
|
||||
*
|
||||
* @param <T> The type of the root value being listed, e.g. {@link ReservationType}.
|
||||
* @param <R> The type of domain label entry being listed, e.g. {@link ReservedListEntry} (note,
|
||||
* must subclass {@link DomainLabelEntry}.
|
||||
*/
|
||||
public abstract class BaseDomainLabelList<T extends Comparable<?>, R extends DomainLabelEntry<T, ?>>
|
||||
extends ImmutableObject implements Buildable {
|
||||
|
||||
@Id
|
||||
String name;
|
||||
|
||||
@Parent
|
||||
Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
|
||||
DateTime creationTime;
|
||||
|
||||
DateTime lastUpdateTime;
|
||||
|
||||
String description;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DateTime getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public DateTime getLastUpdateTime() {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the list CSV data into a map of labels to parsed data of type R.
|
||||
*
|
||||
* @param lines the CSV file, line by line
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected ImmutableMap<String, R> parse(Iterable<String> lines) {
|
||||
Map<String, R> labelsToEntries = new HashMap<>();
|
||||
for (String line : lines) {
|
||||
R entry = createFromLine(line);
|
||||
if (entry == null) {
|
||||
continue;
|
||||
}
|
||||
String label = entry.getLabel();
|
||||
// Adds the label to the list of all labels if it (a) doesn't already exist, or (b) already
|
||||
// exists, but the new value has higher priority (as determined by sort order).
|
||||
labelsToEntries.put(
|
||||
label, Ordering.natural().nullsFirst().max(labelsToEntries.get(label), entry));
|
||||
}
|
||||
return ImmutableMap.copyOf(labelsToEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new entry in the label list from the given line of text. Returns null if the line is
|
||||
* empty and does not contain an entry.
|
||||
*
|
||||
* @throws IllegalArgumentException if the line cannot be parsed correctly.
|
||||
*/
|
||||
@Nullable
|
||||
abstract R createFromLine(String line);
|
||||
|
||||
/**
|
||||
* Helper function to extract the comment from an input line. Returns a list containing the line
|
||||
* (sans comment) and the comment (in that order). If the line was blank or empty, then this
|
||||
* method returns an empty list.
|
||||
*/
|
||||
protected static List<String> splitOnComment(String line) {
|
||||
String comment = "";
|
||||
int index = line.indexOf('#');
|
||||
if (index != -1) {
|
||||
comment = line.substring(index + 1).trim();
|
||||
line = line.substring(0, index).trim();
|
||||
} else {
|
||||
line = line.trim();
|
||||
}
|
||||
return line.isEmpty() ? ImmutableList.<String>of() : ImmutableList.<String>of(line, comment);
|
||||
}
|
||||
|
||||
/** Gets the names of the tlds that reference this list. */
|
||||
public final ImmutableSet<String> getReferencingTlds() {
|
||||
ImmutableSet.Builder<String> builder = new ImmutableSet.Builder<>();
|
||||
Key<? extends BaseDomainLabelList<?, ?>> key = Key.create(this);
|
||||
for (String tld : getTlds()) {
|
||||
if (hasReference(Registry.get(tld), key)) {
|
||||
builder.add(tld);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
protected abstract boolean hasReference(
|
||||
Registry registry, Key<? extends BaseDomainLabelList<?, ?>> key);
|
||||
|
||||
protected static <R> Optional<R> getFromCache(String listName, LoadingCache<String, R> cache) {
|
||||
try {
|
||||
return Optional.of(cache.get(listName));
|
||||
} catch (InvalidCacheLoadException e) {
|
||||
return Optional.absent();
|
||||
} catch (ExecutionException e) {
|
||||
throw new UncheckedExecutionException("Could not retrieve list named " + listName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Base builder for derived classes of {@link BaseDomainLabelList}. */
|
||||
public abstract static class Builder<T extends BaseDomainLabelList<?, ?>, B extends Builder<T, ?>>
|
||||
extends GenericBuilder<T, B> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
protected Builder(T instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
public B setName(String name) {
|
||||
getInstance().name = name;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setCreationTime(DateTime creationTime) {
|
||||
getInstance().creationTime = creationTime;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setLastUpdateTime(DateTime lastUpdateTime) {
|
||||
getInstance().lastUpdateTime = lastUpdateTime;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDescription(String description) {
|
||||
getInstance().description = description;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
checkArgument(!isNullOrEmpty(getInstance().name), "List must have a name");
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// 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 com.google.domain.registry.model.registry.label;
|
||||
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.domain.registry.model.Buildable.GenericBuilder;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
|
||||
/**
|
||||
* Represents a label entry parsed from a line in a Reserved List txt file.
|
||||
*
|
||||
* @param <T> The type of the value stored for the domain label, e.g. {@link ReservationType}.
|
||||
*/
|
||||
public abstract class DomainLabelEntry<T extends Comparable<?>, D extends DomainLabelEntry<?, ?>>
|
||||
extends ImmutableObject implements Comparable<D> {
|
||||
|
||||
@Id
|
||||
String label;
|
||||
|
||||
String comment;
|
||||
|
||||
/**
|
||||
* Returns the label of the field, which also happens to be used as the key for the Map object
|
||||
* that is serialized from Datastore.
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the field (used for determining which entry takes priority over another).
|
||||
*/
|
||||
public abstract T getValue();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public int compareTo(D other) {
|
||||
return ((Comparable<Object>) getValue()).compareTo(other.getValue());
|
||||
}
|
||||
|
||||
/** A generic builder base. */
|
||||
public abstract static class Builder<T extends DomainLabelEntry<?, ?>, B extends Builder<T, ?>>
|
||||
extends GenericBuilder<T, B> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
protected Builder(T instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
public B setLabel(String label) {
|
||||
getInstance().label = label;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setComment(String comment) {
|
||||
getInstance().comment = comment;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
checkArgumentNotNull(emptyToNull(getInstance().label), "Label must be specified");
|
||||
checkArgumentNotNull(getInstance().getValue(), "Value must be specified");
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
}
|
384
java/google/registry/model/registry/label/PremiumList.java
Normal file
384
java/google/registry/model/registry/label/PremiumList.java
Normal file
|
@ -0,0 +1,384 @@
|
|||
// 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 com.google.domain.registry.model.registry.label;
|
||||
|
||||
import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.Iterables.partition;
|
||||
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.allocateId;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import com.google.appengine.api.datastore.EntityNotFoundException;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.model.Buildable;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
import com.google.domain.registry.model.annotations.VirtualEntity;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.VoidWork;
|
||||
import com.googlecode.objectify.Work;
|
||||
import com.googlecode.objectify.annotation.Cache;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A premium list entity, persisted to Datastore, that is used to check domain label prices.
|
||||
*/
|
||||
@Entity
|
||||
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
|
||||
public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.PremiumListEntry> {
|
||||
|
||||
/** The number of premium list entry entities that are created and deleted per batch. */
|
||||
private static final int TRANSACTION_BATCH_SIZE = 200;
|
||||
|
||||
/** Stores the revision key for the set of currently used premium list entry entities. */
|
||||
Key<PremiumListRevision> revisionKey;
|
||||
|
||||
@Ignore
|
||||
Map<String, PremiumListEntry> premiumListMap;
|
||||
|
||||
/** Virtual parent entity for premium list entry entities associated with a single revision. */
|
||||
@Entity
|
||||
@VirtualEntity
|
||||
public static class PremiumListRevision extends ImmutableObject {
|
||||
@Parent
|
||||
Key<PremiumList> parent;
|
||||
|
||||
@Id
|
||||
long revisionId;
|
||||
|
||||
static Key<PremiumListRevision> createKey(PremiumList parent) {
|
||||
PremiumListRevision revision = new PremiumListRevision();
|
||||
revision.parent = Key.create(parent);
|
||||
revision.revisionId = allocateId();
|
||||
return Key.create(revision);
|
||||
}
|
||||
}
|
||||
|
||||
private static LoadingCache<String, PremiumList> cache = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(
|
||||
RegistryEnvironment.get().config().getDomainLabelListCacheDuration().getMillis(),
|
||||
MILLISECONDS)
|
||||
.build(new CacheLoader<String, PremiumList>() {
|
||||
@Override
|
||||
public PremiumList load(final String listName) {
|
||||
return ofy().doTransactionless(new Work<PremiumList>() {
|
||||
@Override
|
||||
public PremiumList run() {
|
||||
return ofy().load()
|
||||
.type(PremiumList.class)
|
||||
.parent(getCrossTldKey())
|
||||
.id(listName)
|
||||
.now();
|
||||
}});
|
||||
}});
|
||||
|
||||
/**
|
||||
* Gets the premium price for the specified label on the specified tld, or returns Optional.absent
|
||||
* if there is no premium price.
|
||||
*/
|
||||
public static Optional<Money> getPremiumPrice(String label, String tld) {
|
||||
Registry registry = Registry.get(checkNotNull(tld, "tld"));
|
||||
if (registry.getPremiumList() == null) {
|
||||
return Optional.<Money> absent();
|
||||
}
|
||||
String listName = registry.getPremiumList().getName();
|
||||
Optional<PremiumList> premiumList = get(listName);
|
||||
if (!premiumList.isPresent()) {
|
||||
throw new IllegalStateException("Could not load premium list named " + listName);
|
||||
}
|
||||
return premiumList.get().getPremiumPrice(label);
|
||||
}
|
||||
|
||||
@OnLoad
|
||||
private void loadPremiumListMap() {
|
||||
try {
|
||||
ImmutableMap.Builder<String, PremiumListEntry> entriesMap = new ImmutableMap.Builder<>();
|
||||
if (revisionKey != null) {
|
||||
for (PremiumListEntry entry : loadEntriesForCurrentRevision()) {
|
||||
entriesMap.put(entry.getLabel(), entry);
|
||||
}
|
||||
}
|
||||
premiumListMap = entriesMap.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not retrieve entries for premium list " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the premium price for the specified label in the current PremiumList, or returns
|
||||
* Optional.absent if there is no premium price.
|
||||
*/
|
||||
public Optional<Money> getPremiumPrice(String label) {
|
||||
return Optional.fromNullable(
|
||||
premiumListMap.containsKey(label) ? premiumListMap.get(label).getValue() : null);
|
||||
}
|
||||
|
||||
public Map<String, PremiumListEntry> getPremiumListEntries() {
|
||||
return nullToEmptyImmutableCopy(premiumListMap);
|
||||
}
|
||||
|
||||
public Key<PremiumListRevision> getRevisionKey() {
|
||||
return revisionKey;
|
||||
}
|
||||
|
||||
/** Returns the PremiumList with the specified name. */
|
||||
public static Optional<PremiumList> get(String name) {
|
||||
try {
|
||||
return Optional.of(cache.get(name));
|
||||
} catch (InvalidCacheLoadException e) {
|
||||
return Optional.<PremiumList> absent();
|
||||
} catch (ExecutionException e) {
|
||||
throw new UncheckedExecutionException("Could not retrieve premium list named " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a PremiumList of the given name exists, without going through the overhead
|
||||
* of loading up all of the premium list entities. Also does not hit the cache.
|
||||
*/
|
||||
public static boolean exists(String name) {
|
||||
try {
|
||||
// Use DatastoreService to bypass the @OnLoad method that loads the premium list entries.
|
||||
getDatastoreService().get(Key.create(getCrossTldKey(), PremiumList.class, name).getRaw());
|
||||
return true;
|
||||
} catch (EntityNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A premium list entry entity, persisted to Datastore. Each instance represents the price of a
|
||||
* single label on a given TLD.
|
||||
*/
|
||||
@Entity
|
||||
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
|
||||
public static class PremiumListEntry extends DomainLabelEntry<Money, PremiumListEntry>
|
||||
implements Buildable {
|
||||
|
||||
@Parent
|
||||
Key<PremiumListRevision> parent;
|
||||
|
||||
Money price;
|
||||
|
||||
@Override
|
||||
public Money getValue() {
|
||||
return price;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for constructing {@link PremiumList} objects, since they are immutable.
|
||||
*/
|
||||
public static class Builder extends DomainLabelEntry.Builder<PremiumListEntry, Builder> {
|
||||
public Builder() {}
|
||||
|
||||
private Builder(PremiumListEntry instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
public Builder setParent(Key<PremiumListRevision> parentKey) {
|
||||
getInstance().parent = parentKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPrice(Money price) {
|
||||
getInstance().price = price;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
PremiumListEntry createFromLine(String originalLine) {
|
||||
List<String> lineAndComment = splitOnComment(originalLine);
|
||||
if (lineAndComment.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String line = lineAndComment.get(0);
|
||||
String comment = lineAndComment.get(1);
|
||||
List<String> parts = Splitter.on(',').trimResults().splitToList(line);
|
||||
checkArgument(parts.size() == 2, "Could not parse line in premium list: %s", originalLine);
|
||||
return new PremiumListEntry.Builder()
|
||||
.setLabel(parts.get(0))
|
||||
.setPrice(Money.parse(parts.get(1)))
|
||||
.setComment(comment)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a PremiumList object to Datastore.
|
||||
*
|
||||
* <p> The flow here is: save the new premium list entries parented on that revision entity,
|
||||
* save/update the PremiumList, and then delete the old premium list entries associated with the
|
||||
* old revision.
|
||||
*/
|
||||
public PremiumList saveAndUpdateEntries() {
|
||||
final Optional<PremiumList> oldPremiumList = get(name);
|
||||
// Save the new child entities in a series of transactions.
|
||||
for (final List<PremiumListEntry> batch
|
||||
: partition(premiumListMap.values(), TRANSACTION_BATCH_SIZE)) {
|
||||
ofy().transactNew(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
ofy().save().entities(batch);
|
||||
}});
|
||||
}
|
||||
// Save the new PremiumList itself.
|
||||
PremiumList updated = ofy().transactNew(new Work<PremiumList>() {
|
||||
@Override
|
||||
public PremiumList run() {
|
||||
DateTime now = ofy().getTransactionTime();
|
||||
// Assert that the premium list hasn't been changed since we started this process.
|
||||
checkState(
|
||||
Objects.equals(
|
||||
ofy().load().type(PremiumList.class).parent(getCrossTldKey()).id(name).now(),
|
||||
oldPremiumList.orNull()),
|
||||
"PremiumList was concurrently edited");
|
||||
PremiumList newList = PremiumList.this.asBuilder()
|
||||
.setLastUpdateTime(now)
|
||||
.setCreationTime(
|
||||
oldPremiumList.isPresent() ? oldPremiumList.get().creationTime : now)
|
||||
.build();
|
||||
ofy().save().entity(newList);
|
||||
return newList;
|
||||
}});
|
||||
// Update the cache.
|
||||
PremiumList.cache.put(name, updated);
|
||||
// Delete the entities under the old PremiumList, if any.
|
||||
if (oldPremiumList.isPresent()) {
|
||||
oldPremiumList.get().deleteEntries();
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasReference(Registry registry, Key<? extends BaseDomainLabelList<?, ?>> key) {
|
||||
return Objects.equals(registry.getPremiumList(), key);
|
||||
}
|
||||
|
||||
/** Deletes the PremiumList and all of its child entities. */
|
||||
public void delete() {
|
||||
ofy().transactNew(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
ofy().delete().entity(PremiumList.this);
|
||||
}});
|
||||
deleteEntries();
|
||||
cache.invalidate(name);
|
||||
}
|
||||
|
||||
private void deleteEntries() {
|
||||
if (revisionKey == null) {
|
||||
return;
|
||||
}
|
||||
for (final List<Key<PremiumListEntry>> batch : partition(
|
||||
loadEntriesForCurrentRevision().keys(),
|
||||
TRANSACTION_BATCH_SIZE)) {
|
||||
ofy().transactNew(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
ofy().delete().keys(batch);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
private Query<PremiumListEntry> loadEntriesForCurrentRevision() {
|
||||
return ofy().load().type(PremiumListEntry.class).ancestor(revisionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link PremiumList} objects, since they are immutable. */
|
||||
public static class Builder extends BaseDomainLabelList.Builder<PremiumList, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
private Builder(PremiumList instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
private boolean entriesWereUpdated;
|
||||
|
||||
public Builder setPremiumListMap(ImmutableMap<String, PremiumListEntry> premiumListMap) {
|
||||
entriesWereUpdated = true;
|
||||
getInstance().premiumListMap = premiumListMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Updates the premiumListMap from input lines. */
|
||||
public Builder setPremiumListMapFromLines(Iterable<String> lines) {
|
||||
return setPremiumListMap(getInstance().parse(lines));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PremiumList build() {
|
||||
final PremiumList instance = getInstance();
|
||||
if (getInstance().revisionKey == null || entriesWereUpdated) {
|
||||
getInstance().revisionKey = PremiumListRevision.createKey(instance);
|
||||
}
|
||||
// When we build an instance, make sure all entries are parented on its revisionKey.
|
||||
instance.premiumListMap = Maps.transformValues(
|
||||
nullToEmpty(instance.premiumListMap),
|
||||
new Function<PremiumListEntry, PremiumListEntry>() {
|
||||
@Override
|
||||
public PremiumListEntry apply(PremiumListEntry entry) {
|
||||
return entry.asBuilder().setParent(instance.revisionKey).build();
|
||||
}});
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// 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 com.google.domain.registry.model.registry.label;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Enum describing reservation on a label in a {@link ReservedList} */
|
||||
public enum ReservationType {
|
||||
|
||||
// We explicitly set the severity, even though we have a checkState that makes it equal to the
|
||||
// ordinal, so that no one accidentally reorders these values and changes the sort order.
|
||||
|
||||
UNRESERVED(null, 0),
|
||||
ALLOWED_IN_SUNRISE("Reserved for non-sunrise", 1),
|
||||
MISTAKEN_PREMIUM("Reserved", 2),
|
||||
RESERVED_FOR_ANCHOR_TENANT("Reserved", 3),
|
||||
NAME_COLLISION("Cannot be delegated", 4),
|
||||
FULLY_BLOCKED("Reserved", 5);
|
||||
|
||||
@Nullable
|
||||
private final String messageForCheck;
|
||||
|
||||
ReservationType(@Nullable String messageForCheck, int severity) {
|
||||
this.messageForCheck = messageForCheck;
|
||||
checkState(ordinal() == severity);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessageForCheck() {
|
||||
return messageForCheck;
|
||||
}
|
||||
}
|
317
java/google/registry/model/registry/label/ReservedList.java
Normal file
317
java/google/registry/model/registry/label/ReservedList.java
Normal file
|
@ -0,0 +1,317 @@
|
|||
// 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 com.google.domain.registry.model.registry.label;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
|
||||
import static com.google.domain.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
|
||||
import static com.google.domain.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT;
|
||||
import static com.google.domain.registry.model.registry.label.ReservationType.UNRESERVED;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.VoidWork;
|
||||
import com.googlecode.objectify.annotation.Cache;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Mapify;
|
||||
import com.googlecode.objectify.mapper.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A reserved list entity, persisted to Datastore, that is used to check domain label reservations.
|
||||
*/
|
||||
@Entity
|
||||
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
|
||||
public final class ReservedList
|
||||
extends BaseDomainLabelList<ReservationType, ReservedList.ReservedListEntry> {
|
||||
|
||||
@Mapify(ReservedListEntry.LabelMapper.class)
|
||||
Map<String, ReservedListEntry> reservedListMap;
|
||||
|
||||
boolean shouldPublish = true;
|
||||
|
||||
/**
|
||||
* A reserved list entry entity, persisted to Datastore, that represents a single label and its
|
||||
* reservation type.
|
||||
*/
|
||||
@Embed
|
||||
public static class ReservedListEntry
|
||||
extends DomainLabelEntry<ReservationType, ReservedListEntry> {
|
||||
|
||||
ReservationType reservationType;
|
||||
|
||||
/**
|
||||
* Contains the auth code necessary to register a domain with this label.
|
||||
* Note that this field will only ever be populated for entries with type
|
||||
* RESERVED_FOR_ANCHOR_TENANT.
|
||||
*/
|
||||
String authCode;
|
||||
|
||||
/** Mapper for use with @Mapify */
|
||||
static class LabelMapper implements Mapper<String, ReservedListEntry> {
|
||||
@Override
|
||||
public String getKey(ReservedListEntry entry) {
|
||||
return entry.getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
public static ReservedListEntry create(
|
||||
String label,
|
||||
ReservationType reservationType,
|
||||
@Nullable String authCode,
|
||||
String comment) {
|
||||
if (authCode != null) {
|
||||
checkArgument(reservationType == RESERVED_FOR_ANCHOR_TENANT,
|
||||
"Only anchor tenant reservations should have an auth code configured");
|
||||
} else {
|
||||
checkArgument(reservationType != RESERVED_FOR_ANCHOR_TENANT,
|
||||
"Anchor tenant reservations must have an auth code configured");
|
||||
}
|
||||
ReservedListEntry entry = new ReservedListEntry();
|
||||
entry.label = label;
|
||||
entry.reservationType = reservationType;
|
||||
entry.authCode = authCode;
|
||||
entry.comment = comment;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReservationType getValue() {
|
||||
return reservationType;
|
||||
}
|
||||
|
||||
public String getAuthCode() {
|
||||
return authCode;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasReference(Registry registry, Key<? extends BaseDomainLabelList<?, ?>> key) {
|
||||
return registry.getReservedLists().contains(key);
|
||||
}
|
||||
|
||||
/** Determines whether the ReservedList is in use on any Registry */
|
||||
public boolean isInUse() {
|
||||
return !getReferencingTlds().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this reserved list is included in the concatenated list of reserved terms
|
||||
* published to Google Drive for viewing by registrars.
|
||||
*/
|
||||
public boolean getShouldPublish() {
|
||||
return shouldPublish;
|
||||
}
|
||||
|
||||
public ImmutableMap<String, ReservedListEntry> getReservedListEntries() {
|
||||
return ImmutableMap.copyOf(nullToEmpty(reservedListMap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a ReservedList by name using the caching layer.
|
||||
*
|
||||
* @return An Optional<ReservedList> that has a value if a reserved list exists by the given
|
||||
* name, or absent if not.
|
||||
* @throws UncheckedExecutionException if some other error occurs while trying to load the
|
||||
* ReservedList from the cache or Datastore.
|
||||
*/
|
||||
public static Optional<ReservedList> get(String listName) {
|
||||
return getFromCache(listName, cache);
|
||||
}
|
||||
|
||||
/** Loads a ReservedList from its Objectify key. */
|
||||
public static Optional<ReservedList> load(Key<ReservedList> key) {
|
||||
return get(key.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the set of all reserved lists associated with the specified tld and returns the
|
||||
* reservation type of the first occurrence of label seen. If the label is in none of the lists,
|
||||
* it returns UNRESERVED.
|
||||
*/
|
||||
public static ReservationType getReservation(String label, String tld) {
|
||||
checkNotNull(label, "label");
|
||||
if (label.length() == 0 || label.length() == 2) {
|
||||
return FULLY_BLOCKED; // All 2-letter labels are FULLY_BLOCKED.
|
||||
}
|
||||
ReservedListEntry entry = getReservedListEntry(label, tld);
|
||||
return (entry != null) ? entry.reservationType : UNRESERVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given label and TLD is reserved for an anchor tenant, and the given
|
||||
* auth code matches the one set on the reservation.
|
||||
*/
|
||||
public static boolean matchesAnchorTenantReservation(String label, String tld, String authCode) {
|
||||
ReservedListEntry entry = getReservedListEntry(label, tld);
|
||||
return entry != null
|
||||
&& entry.reservationType == RESERVED_FOR_ANCHOR_TENANT
|
||||
&& Objects.equals(entry.getAuthCode(), authCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to retrieve the entry associated with this label and TLD, or null if no such
|
||||
* entry exists.
|
||||
*/
|
||||
@Nullable
|
||||
private static ReservedListEntry getReservedListEntry(String label, String tld) {
|
||||
Registry registry = Registry.get(checkNotNull(tld, "tld"));
|
||||
ImmutableSet<Key<ReservedList>> reservedLists = registry.getReservedLists();
|
||||
ImmutableSet<ReservedList> lists = loadReservedLists(reservedLists);
|
||||
ReservedListEntry entry = null;
|
||||
|
||||
// Loop through all reservation lists and check each one for the inputted label, and return
|
||||
// the most severe ReservationType found.
|
||||
for (ReservedList rl : lists) {
|
||||
Map<String, ReservedListEntry> entries = rl.getReservedListEntries();
|
||||
ReservedListEntry nextEntry = entries.get(label);
|
||||
if (nextEntry != null
|
||||
&& (entry == null || nextEntry.reservationType.compareTo(entry.reservationType) > 0)) {
|
||||
entry = nextEntry;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private static ImmutableSet<ReservedList> loadReservedLists(
|
||||
ImmutableSet<Key<ReservedList>> reservedListKeys) {
|
||||
ImmutableSet.Builder<ReservedList> builder = new ImmutableSet.Builder<>();
|
||||
for (Key<ReservedList> listKey : reservedListKeys) {
|
||||
try {
|
||||
builder.add(cache.get(listKey.getName()));
|
||||
} catch (ExecutionException e) {
|
||||
throw new UncheckedExecutionException(String.format(
|
||||
"Could not load the reserved list '%s' from the cache", listKey.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static LoadingCache<String, ReservedList> cache = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(
|
||||
RegistryEnvironment.get().config().getDomainLabelListCacheDuration().getMillis(),
|
||||
MILLISECONDS)
|
||||
.build(new CacheLoader<String, ReservedList>() {
|
||||
@Override
|
||||
public ReservedList load(String listName) {
|
||||
return ofy().load().type(ReservedList.class).parent(getCrossTldKey()).id(listName).now();
|
||||
}});
|
||||
|
||||
/** Deletes the ReservedList with the given name. */
|
||||
public static void delete(final String listName) {
|
||||
final ReservedList reservedList = ReservedList.get(listName).orNull();
|
||||
checkState(
|
||||
reservedList != null,
|
||||
"Attempted to delete reserved list %s which doesn't exist",
|
||||
listName);
|
||||
ofy().transactNew(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
ofy().delete().entity(reservedList).now();
|
||||
}
|
||||
});
|
||||
cache.invalidate(listName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ReservationType} of a label in a single ReservedList, or returns an absent
|
||||
* Optional if none exists in the list.
|
||||
*
|
||||
* <p>Note that this logic is significantly less complicated than the getReservation() methods,
|
||||
* which are applicable to an entire Registry, and need to check across multiple reserved lists.
|
||||
*/
|
||||
public Optional<ReservationType> getReservationInList(String label) {
|
||||
ReservedListEntry entry = getReservedListEntries().get(label);
|
||||
return Optional.fromNullable(entry == null ? null : entry.reservationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
ReservedListEntry createFromLine(String originalLine) {
|
||||
List<String> lineAndComment = splitOnComment(originalLine);
|
||||
if (lineAndComment.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String line = lineAndComment.get(0);
|
||||
String comment = lineAndComment.get(1);
|
||||
List<String> parts = Splitter.on(',').trimResults().splitToList(line);
|
||||
checkArgument(parts.size() == 2 || parts.size() == 3,
|
||||
"Could not parse line in reserved list: %s", originalLine);
|
||||
String label = parts.get(0);
|
||||
ReservationType reservationType = ReservationType.valueOf(parts.get(1));
|
||||
String authCode = (parts.size() > 2) ? parts.get(2) : null;
|
||||
return ReservedListEntry.create(label, reservationType, authCode, comment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for constructing {@link ReservedList} objects, since they are immutable.
|
||||
*/
|
||||
public static class Builder extends BaseDomainLabelList.Builder<ReservedList, Builder> {
|
||||
public Builder() {}
|
||||
|
||||
private Builder(ReservedList instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
public Builder setReservedListMap(ImmutableMap<String, ReservedListEntry> reservedListMap) {
|
||||
getInstance().reservedListMap = reservedListMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setShouldPublish(boolean shouldPublish) {
|
||||
getInstance().shouldPublish = shouldPublish;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the reservedListMap from input lines.
|
||||
*
|
||||
* @throws IllegalArgumentException if the lines cannot be parsed correctly.
|
||||
*/
|
||||
public Builder setReservedListMapFromLines(Iterable<String> lines) {
|
||||
return setReservedListMap(getInstance().parse(lines));
|
||||
}
|
||||
}
|
||||
}
|
16
java/google/registry/model/registry/label/package-info.java
Normal file
16
java/google/registry/model/registry/label/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// 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.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.model.registry.label;
|
Loading…
Add table
Add a link
Reference in a new issue