// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.domain;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.model.domain.DesignatedContact.Type.REGISTRANT;
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.getTldFromSld;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource;
import google.registry.model.EppResourceUtils;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.host.HostResource;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlTransient;
/** Shared base class for {@link DomainResource} and {@link DomainApplication}. */
@XmlTransient
@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
@XmlElement(name = "name")
String fullyQualifiedDomainName;
/** The top level domain this is under, dernormalized from {@link #fullyQualifiedDomainName}. */
@Index
@XmlTransient
String tld;
/** References to hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
//TODO(b/28713909): Make this a Set[>.
Set> nameservers;
/**
* Associated contacts for the domain (other than registrant).
* ]
* This field is marked with {@literal @}Ignore so that {@link DomainBase} subclasses won't
* persist it. Instead, the data in this field and in the {@link #registrant} are both stored in
* {@link DomainBase#allContacts} to allow for more efficient queries.
*/
@Ignore
@XmlElement(name = "contact")
Set contacts;
/**
* The union of the contacts in {@link #contacts} and {@link #registrant}. This is so we can query
* across all contacts at once. It is maintained by the builder and by {@link #unpackageContacts}.
*/
@XmlTransient
Set allContacts;
/**
* A reference to the registrant who registered this domain.
*
* This field is marked with {@literal @}Ignore so that {@link DomainBase} subclasses won't
* persist it. Instead, the data in this field and in the {@link DomainBase#contacts} are
* both stored in {@link DomainBase#allContacts} to allow for more efficient queries.
*/
@Ignore
//TODO(b/28713909): Make this a Ref.
ReferenceUnion registrant;
/** 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.
*/
@XmlTransient
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)
@XmlTransient
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)
@XmlTransient
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() {
ImmutableSet.Builder][> builder = new ImmutableSet.Builder<>();
for (ReferenceUnion union : nullToEmptyImmutableCopy(nameservers)) {
builder.add(union.getLinked());
}
return builder.build();
}
/** Loads and returns all linked nameservers. */
public ImmutableSet loadNameservers() {
return EppResourceUtils.loadReferencedNameservers(getNameservers());
}
public Ref getRegistrant() {
return registrant == null ? null : registrant.getLinked();
}
public ContactResource loadRegistrant() {
return getRegistrant().get();
}
public ImmutableSet getContacts() {
return nullToEmptyImmutableCopy(contacts);
}
public AuthInfo 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.getContactRef());
}
return contactsBuilder.build();
}
/** Loads and returns all referenced contacts from this domain or application. */
public ImmutableSet loadReferencedContacts() {
return EppResourceUtils.loadReferencedContacts(getReferencedContacts());
}
public String getTld() {
return tld;
}
/**
* On load, unpackage the {@link #allContacts} field, placing the registrant into
* {@link #registrant} field and all other contacts into {@link #contacts}.
*/
@OnLoad
void unpackageContacts() {
ImmutableSet.Builder contactsBuilder = new ImmutableSet.Builder<>();
for (DesignatedContact contact : nullToEmptyImmutableCopy(allContacts)) {
if (REGISTRANT.equals(contact.getType())){
registrant = ReferenceUnion.create(contact.getContactRef());
} else {
contactsBuilder.add(contact);
}
}
contacts = contactsBuilder.build();
}
/** 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();
checkState(
!isNullOrEmpty(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName");
instance.tld = getTldFromSld(instance.fullyQualifiedDomainName);
instance.allContacts = instance.registrant == null
? instance.contacts
: union(
instance.getContacts(),
DesignatedContact.create(REGISTRANT, instance.registrant.getLinked()));
return super.build();
}
public B setFullyQualifiedDomainName(String fullyQualifiedDomainName) {
getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName;
return thisCastToDerived();
}
public B setDsData(ImmutableSet dsData) {
getInstance().dsData = dsData;
return thisCastToDerived();
}
public B setRegistrant(Ref registrant) {
getInstance().registrant = ReferenceUnion.create(registrant);
return thisCastToDerived();
}
public B setAuthInfo(DomainAuthInfo authInfo) {
getInstance().authInfo = authInfo;
return thisCastToDerived();
}
public B setNameservers(ImmutableSet][> nameservers) {
ImmutableSet.Builder> builder = new ImmutableSet.Builder<>();
for (Ref ref : nullToEmpty(nameservers)) {
builder.add(ReferenceUnion.create(ref));
}
getInstance().nameservers = builder.build();
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) {
getInstance().contacts = contacts;
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();
}
}
}
]