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:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View file

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

View file

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

View 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();
}
}
}

View file

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

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

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