// Copyright 2017 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package google.registry.model.domain; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Predicates.not; import static com.google.common.base.Strings.emptyToNull; import static com.google.common.collect.Iterables.all; import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.union; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.CollectionUtils.nullToEmpty; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy; import static google.registry.util.CollectionUtils.union; import static google.registry.util.DomainNameUtils.canonicalizeDomainName; import static google.registry.util.DomainNameUtils.getTldFromDomainName; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Ordering; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.IgnoreSave; import com.googlecode.objectify.annotation.Index; import com.googlecode.objectify.condition.IfNull; import google.registry.model.EppResource; import google.registry.model.annotations.ReportedOn; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact.Type; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.host.HostResource; import java.util.Set; /** Shared base class for {@link DomainResource} and {@link DomainApplication}. */ @ReportedOn @Entity public abstract class DomainBase extends EppResource { /** * Fully qualified domain name (puny-coded), which serves as the foreign key for this domain. * *

This is only unique in the sense that for any given lifetime specified as the time range * from (creationTime, deletionTime) there can only be one domain in the datastore with this name. * However, there can be many domains with the same name and non-overlapping lifetimes. * * @invariant fullyQualifiedDomainName == fullyQualifiedDomainName.toLowerCase() */ @Index String fullyQualifiedDomainName; /** The top level domain this is under, dernormalized from {@link #fullyQualifiedDomainName}. */ @Index String tld; /** References to hosts that are the nameservers for the domain. */ @Index Set> nsHosts; /** * The union of the contacts visible via {@link #getContacts} and {@link #getRegistrant}. * *

These are stored in one field so that we can query across all contacts at once. */ Set allContacts; /** Authorization info (aka transfer secret) of the domain. */ DomainAuthInfo authInfo; /** * Data used to construct DS records for this domain. * *

This is {@literal @}XmlTransient because it needs to be returned under the "extension" tag * of an info response rather than inside the "infData" tag. */ Set dsData; /** * The claims notice supplied when this application or domain was created, if there was one. It's * {@literal @}XmlTransient because it's not returned in an info response. */ @IgnoreSave(IfNull.class) LaunchNotice launchNotice; /** * Name of first IDN table associated with TLD that matched the characters in this domain label. * * @see google.registry.tldconfig.idn.IdnLabelValidator#findValidIdnTableForTld */ @IgnoreSave(IfNull.class) String idnTableName; public String getFullyQualifiedDomainName() { return fullyQualifiedDomainName; } public ImmutableSortedSet getDsData() { return nullToEmptyImmutableSortedCopy(dsData); } public LaunchNotice getLaunchNotice() { return launchNotice; } public String getIdnTableName() { return idnTableName; } public ImmutableSet> getNameservers() { return nullToEmptyImmutableCopy(nsHosts); } /** Loads and returns the fully qualified host names of all linked nameservers. */ public ImmutableSortedSet loadNameserverFullyQualifiedHostNames() { return FluentIterable.from(ofy().load().keys(getNameservers()).values()) .transform( new Function() { @Override public String apply(HostResource host) { return host.getFullyQualifiedHostName(); } }) .toSortedSet(Ordering.natural()); } /** A key to the registrant who registered this domain. */ public Key getRegistrant() { return FluentIterable .from(nullToEmpty(allContacts)) .filter(IS_REGISTRANT) .first() .get() .getContactKey(); } /** Associated contacts for the domain (other than registrant). */ public ImmutableSet getContacts() { return FluentIterable .from(nullToEmpty(allContacts)) .filter(not(IS_REGISTRANT)) .toSet(); } public DomainAuthInfo getAuthInfo() { return authInfo; } /** Returns all referenced contacts from this domain or application. */ public ImmutableSet> getReferencedContacts() { ImmutableSet.Builder> contactsBuilder = new ImmutableSet.Builder<>(); for (DesignatedContact designated : nullToEmptyImmutableCopy(allContacts)) { contactsBuilder.add(designated.getContactKey()); } return contactsBuilder.build(); } public String getTld() { return tld; } /** Predicate to determine if a given {@link DesignatedContact} is the registrant. */ private static final Predicate IS_REGISTRANT = new Predicate() { @Override public boolean apply(DesignatedContact contact) { return DesignatedContact.Type.REGISTRANT.equals(contact.type); }}; /** An override of {@link EppResource#asBuilder} with tighter typing. */ @Override public abstract Builder asBuilder(); /** A builder for constructing {@link DomainBase}, since it is immutable. */ public abstract static class Builder> extends EppResource.Builder { protected Builder() {} protected Builder(T instance) { super(instance); } @Override public T build() { T instance = getInstance(); checkArgumentNotNull( emptyToNull(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName"); checkArgument(any(instance.allContacts, IS_REGISTRANT), "Missing registrant"); instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName); return super.build(); } public B setFullyQualifiedDomainName(String fullyQualifiedDomainName) { checkArgument( fullyQualifiedDomainName.equals(canonicalizeDomainName(fullyQualifiedDomainName)), "Domain name must be in puny-coded, lower-case form"); getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName; return thisCastToDerived(); } public B setDsData(ImmutableSet dsData) { getInstance().dsData = dsData; return thisCastToDerived(); } public B setRegistrant(Key registrant) { // Replace the registrant contact inside allContacts. getInstance().allContacts = union( getInstance().getContacts(), DesignatedContact.create(Type.REGISTRANT, checkArgumentNotNull(registrant))); return thisCastToDerived(); } public B setAuthInfo(DomainAuthInfo authInfo) { getInstance().authInfo = authInfo; return thisCastToDerived(); } public B setNameservers(ImmutableSet> nameservers) { getInstance().nsHosts = nameservers; return thisCastToDerived(); } public B addNameservers(ImmutableSet> nameservers) { return setNameservers( ImmutableSet.copyOf(union(getInstance().getNameservers(), nameservers))); } public B removeNameservers(ImmutableSet> nameservers) { return setNameservers( ImmutableSet.copyOf(difference(getInstance().getNameservers(), nameservers))); } public B setContacts(ImmutableSet contacts) { checkArgument(all(contacts, not(IS_REGISTRANT)), "Registrant cannot be a contact"); // Replace the non-registrant contacts inside allContacts. getInstance().allContacts = FluentIterable .from(nullToEmpty(getInstance().allContacts)) .filter(IS_REGISTRANT) .append(contacts) .toSet(); return thisCastToDerived(); } public B addContacts(ImmutableSet contacts) { return setContacts(ImmutableSet.copyOf(union(getInstance().getContacts(), contacts))); } public B removeContacts(ImmutableSet contacts) { return setContacts(ImmutableSet.copyOf(difference(getInstance().getContacts(), contacts))); } public B setLaunchNotice(LaunchNotice launchNotice) { getInstance().launchNotice = launchNotice; return thisCastToDerived(); } public B setIdnTableName(String idnTableName) { getInstance().idnTableName = idnTableName; return thisCastToDerived(); } } }