diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index aa7ac4d89..745e57298 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -90,7 +90,7 @@ public abstract class RdapActionBase implements Runnable { * @return A map (probably containing nested maps and lists) with the final JSON response data. */ abstract ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException; + String pathSearchString, boolean isHeadRequest, String linkBase); @Override public void run() { diff --git a/java/google/registry/rdap/RdapAutnumAction.java b/java/google/registry/rdap/RdapAutnumAction.java index 15e22a68e..092a7f896 100644 --- a/java/google/registry/rdap/RdapAutnumAction.java +++ b/java/google/registry/rdap/RdapAutnumAction.java @@ -19,7 +19,6 @@ import static google.registry.request.Action.Method.HEAD; import com.google.common.collect.ImmutableMap; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.NotImplementedException; import javax.inject.Inject; @@ -48,7 +47,7 @@ public class RdapAutnumAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { throw new NotImplementedException("Domain Name Registry information only"); } } diff --git a/java/google/registry/rdap/RdapDomainAction.java b/java/google/registry/rdap/RdapDomainAction.java index 181b47165..f8e176dc5 100644 --- a/java/google/registry/rdap/RdapDomainAction.java +++ b/java/google/registry/rdap/RdapDomainAction.java @@ -20,8 +20,8 @@ import static google.registry.request.Action.Method.HEAD; import com.google.common.collect.ImmutableMap; import google.registry.model.domain.DomainResource; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.NotFoundException; import google.registry.util.Clock; import javax.inject.Inject; @@ -50,7 +50,7 @@ public class RdapDomainAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { DateTime now = clock.nowUtc(); pathSearchString = canonicalizeName(pathSearchString); validateDomainName(pathSearchString); @@ -61,6 +61,6 @@ public class RdapDomainAction extends RdapActionBase { throw new NotFoundException(pathSearchString + " not found"); } return RdapJsonFormatter.makeRdapJsonForDomain( - domainResource, true, rdapLinkBase, rdapWhoisServer, now); + domainResource, true, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL); } } diff --git a/java/google/registry/rdap/RdapDomainSearchAction.java b/java/google/registry/rdap/RdapDomainSearchAction.java index 79d70bb29..cc956b8de 100644 --- a/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/java/google/registry/rdap/RdapDomainSearchAction.java @@ -34,14 +34,15 @@ import google.registry.model.EppResourceUtils; import google.registry.model.domain.DomainResource; import google.registry.model.host.HostResource; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.request.Parameter; import google.registry.util.Clock; import google.registry.util.Idn; import java.net.InetAddress; +import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import org.joda.time.DateTime; @@ -60,7 +61,7 @@ import org.joda.time.DateTime; public class RdapDomainSearchAction extends RdapActionBase { public static final String PATH = "/rdap/domains"; - + public static final int CHUNK_SIZE_SCALING_FACTOR = 5; public static final int MAX_CHUNK_FETCHES = 20; @@ -84,7 +85,7 @@ public class RdapDomainSearchAction extends RdapActionBase { /** Parses the parameters and calls the appropriate search function. */ @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/domains?name=exam*.com. // The pathSearchString is not used by search commands. @@ -124,7 +125,8 @@ public class RdapDomainSearchAction extends RdapActionBase { } ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); builder.put("domainSearchResults", results); - RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.DOMAIN, null, rdapLinkBase); + RdapJsonFormatter.addTopLevelEntries( + builder, BoilerplateType.DOMAIN, ImmutableList.of(), ImmutableList.of(), rdapLinkBase); return builder.build(); } @@ -138,9 +140,7 @@ public class RdapDomainSearchAction extends RdapActionBase { if (domainResource == null) { return ImmutableList.of(); } - return ImmutableList.of( - RdapJsonFormatter.makeRdapJsonForDomain( - domainResource, false, rdapLinkBase, rdapWhoisServer, now)); + return makeSearchResults(ImmutableList.of(domainResource), now); // Handle queries with a wildcard. } else { // We can't query for undeleted domains as part of the query itself; that would require an @@ -154,54 +154,49 @@ public class RdapDomainSearchAction extends RdapActionBase { // domains as the result set size limit), then give up and return a result set that is smaller // than the limit. Ugly? You bet! // TODO(b/31546493): Add metrics to figure out how well this algorithm works. - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); - String previousChunkEnd = null; - for (int numResultsFound = 0, retry = 0; - (retry < MAX_CHUNK_FETCHES) && (numResultsFound < rdapResultSetMaxSize); - retry++) { + List domainList = new ArrayList<>(); + String previousChunkEndString = null; + for (int numChunkFetches = 0; + (numChunkFetches < MAX_CHUNK_FETCHES) && (domainList.size() < rdapResultSetMaxSize); + numChunkFetches++) { // Construct the query. Query query = ofy().load() .type(DomainResource.class) .filter("fullyQualifiedDomainName <", partialStringQuery.getNextInitialString()); - if (previousChunkEnd == null) { + if (previousChunkEndString == null) { query = query.filter( "fullyQualifiedDomainName >=", partialStringQuery.getInitialString()); } else { - query = query.filter("fullyQualifiedDomainName >", previousChunkEnd); + query = query.filter("fullyQualifiedDomainName >", previousChunkEndString); } if (partialStringQuery.getSuffix() != null) { query = query.filter("tld", partialStringQuery.getSuffix()); } // Perform the query and weed out deleted domains. - previousChunkEnd = null; + previousChunkEndString = null; int numDomainsInChunk = 0; - for (DomainResource domainResource : + for (DomainResource domain : query.limit(rdapResultSetMaxSize * CHUNK_SIZE_SCALING_FACTOR)) { - previousChunkEnd = domainResource.getFullyQualifiedDomainName(); + previousChunkEndString = domain.getFullyQualifiedDomainName(); numDomainsInChunk++; - if (EppResourceUtils.isActive(domainResource, now)) { - builder.add( - RdapJsonFormatter.makeRdapJsonForDomain( - domainResource, false, rdapLinkBase, rdapWhoisServer, now)); - numResultsFound++; - if (numResultsFound >= rdapResultSetMaxSize) { - return builder.build(); + if (EppResourceUtils.isActive(domain, now)) { + domainList.add(domain); + if (domainList.size() >= rdapResultSetMaxSize) { + break; } } } - if ((previousChunkEnd == null) - || (numDomainsInChunk < rdapResultSetMaxSize * CHUNK_SIZE_SCALING_FACTOR)) { + if (numDomainsInChunk < rdapResultSetMaxSize * CHUNK_SIZE_SCALING_FACTOR) { break; } } - return builder.build(); + return makeSearchResults(domainList, now); } } /** Searches for domains by nameserver name, returning a JSON array of domain info maps. */ private ImmutableList> - searchByNameserverLdhName(final RdapSearchPattern partialStringQuery, final DateTime now) - throws HttpException { + searchByNameserverLdhName(final RdapSearchPattern partialStringQuery, final DateTime now) { Iterable> hostKeys; // Handle queries without a wildcard; just load the host by foreign key in the usual way. if (!partialStringQuery.getHasWildcard()) { @@ -278,19 +273,29 @@ public class RdapDomainSearchAction extends RdapActionBase { private ImmutableList> searchByNameserverRefs(final Iterable> hostKeys, final DateTime now) { // We must break the query up into chunks, because the in operator is limited to 30 subqueries. - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); + ImmutableList.Builder domainListBuilder = new ImmutableList.Builder<>(); for (List> chunk : Iterables.partition(hostKeys, 30)) { - Query query = ofy().load() - .type(DomainResource.class) - .filter("nameservers.linked in", chunk) - .filter("deletionTime >", now) - .limit(1000); - for (DomainResource domainResource : query) { - builder.add( - RdapJsonFormatter.makeRdapJsonForDomain( - domainResource, false, rdapLinkBase, rdapWhoisServer, now)); - } + domainListBuilder.addAll( + ofy().load() + .type(DomainResource.class) + .filter("nameservers.linked in", chunk) + .filter("deletionTime >", now) + .limit(1000)); } - return builder.build(); + return makeSearchResults(domainListBuilder.build(), now); + } + + /** Output JSON for a list of domains. */ + private ImmutableList> makeSearchResults( + List domains, DateTime now) { + OutputDataType outputDataType = + (domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; + ImmutableList.Builder> jsonBuilder = new ImmutableList.Builder<>(); + for (DomainResource domain : domains) { + jsonBuilder.add( + RdapJsonFormatter.makeRdapJsonForDomain( + domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType)); + } + return jsonBuilder.build(); } } diff --git a/java/google/registry/rdap/RdapEntityAction.java b/java/google/registry/rdap/RdapEntityAction.java index 65fae9625..abf268ca8 100644 --- a/java/google/registry/rdap/RdapEntityAction.java +++ b/java/google/registry/rdap/RdapEntityAction.java @@ -26,8 +26,8 @@ import com.googlecode.objectify.Key; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact; import google.registry.model.registrar.Registrar; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.util.Clock; @@ -66,7 +66,7 @@ public class RdapEntityAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { DateTime now = clock.nowUtc(); // The query string is not used; the RDAP syntax is /rdap/entity/handle (the handle is the roid // for contacts and the client identifier for registrars). Since RDAP's concept of an entity @@ -85,7 +85,8 @@ public class RdapEntityAction extends RdapActionBase { Optional.absent(), rdapLinkBase, rdapWhoisServer, - now); + now, + OutputDataType.FULL); } } try { @@ -95,7 +96,7 @@ public class RdapEntityAction extends RdapActionBase { Registrar.loadByIanaIdentifierRange(ianaIdentifier, ianaIdentifier + 1, 1), null); if ((registrar != null) && registrar.isActiveAndPubliclyVisible()) { return RdapJsonFormatter.makeRdapJsonForRegistrar( - registrar, true, rdapLinkBase, rdapWhoisServer, now); + registrar, true, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL); } } catch (NumberFormatException e) { // Although the search string was not a valid IANA identifier, it might still have been a diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index 8a1608b0f..d53d76b62 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -29,13 +29,15 @@ import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact; import google.registry.model.registrar.Registrar; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.Parameter; import google.registry.util.Clock; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; import org.joda.time.DateTime; @@ -73,7 +75,7 @@ public class RdapEntitySearchAction extends RdapActionBase { /** Parses the parameters and calls the appropriate search function. */ @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/entities?fn=Bobby%20Joe*. // The pathSearchString is not used by search commands. @@ -96,10 +98,11 @@ public class RdapEntitySearchAction extends RdapActionBase { if (results.isEmpty()) { throw new NotFoundException("No entities found"); } - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("entitySearchResults", results); - RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.ENTITY, null, rdapLinkBase); - return builder.build(); + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("entitySearchResults", results); + RdapJsonFormatter.addTopLevelEntries( + jsonBuilder, BoilerplateType.ENTITY, ImmutableList.of(), ImmutableList.of(), rdapLinkBase); + return jsonBuilder.build(); } /** @@ -117,7 +120,7 @@ public class RdapEntitySearchAction extends RdapActionBase { * assume that entity names are regular unicode. */ private ImmutableList> - searchByName(final RdapSearchPattern partialStringQuery, DateTime now) throws HttpException { + searchByName(final RdapSearchPattern partialStringQuery, DateTime now) { // Handle queries without a wildcard -- load by name, which may not be unique. if (!partialStringQuery.getHasWildcard()) { Registrar registrar = Registrar.loadByName(partialStringQuery.getInitialString()); @@ -126,7 +129,8 @@ public class RdapEntitySearchAction extends RdapActionBase { .type(ContactResource.class) .filter("searchName", partialStringQuery.getInitialString()) .filter("deletionTime", END_OF_TIME) - .limit(rdapResultSetMaxSize), + .limit(rdapResultSetMaxSize) + .list(), (registrar == null) ? ImmutableList.of() : ImmutableList.of(registrar), now); @@ -141,11 +145,12 @@ public class RdapEntitySearchAction extends RdapActionBase { .filter("searchName >=", partialStringQuery.getInitialString()) .filter("searchName <", partialStringQuery.getNextInitialString()) .filter("deletionTime", END_OF_TIME) - .limit(rdapResultSetMaxSize), - Registrar.loadByNameRange( + .limit(rdapResultSetMaxSize) + .list(), + ImmutableList.copyOf(Registrar.loadByNameRange( partialStringQuery.getInitialString(), partialStringQuery.getNextInitialString(), - rdapResultSetMaxSize), + rdapResultSetMaxSize)), now); // Don't allow suffixes in entity name search queries. } else { @@ -155,7 +160,7 @@ public class RdapEntitySearchAction extends RdapActionBase { /** Searches for entities by handle, returning a JSON array of entity info maps. */ private ImmutableList> searchByHandle( - final RdapSearchPattern partialStringQuery, DateTime now) throws HttpException { + final RdapSearchPattern partialStringQuery, DateTime now) { // Handle queries without a wildcard -- load by ID. if (!partialStringQuery.getHasWildcard()) { ContactResource contactResource = ofy().load() @@ -183,7 +188,8 @@ public class RdapEntitySearchAction extends RdapActionBase { .filterKey( "<", Key.create(ContactResource.class, partialStringQuery.getNextInitialString())) .filter("deletionTime", END_OF_TIME) - .limit(rdapResultSetMaxSize), + .limit(rdapResultSetMaxSize) + .list(), ImmutableList.of(), now); // Don't allow suffixes in entity handle search queries. @@ -207,29 +213,58 @@ public class RdapEntitySearchAction extends RdapActionBase { /** Builds a JSON array of entity info maps based on the specified contacts and registrars. */ private ImmutableList> makeSearchResults( - Iterable contactResources, Iterable registrars, DateTime now) - throws HttpException { - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); - for (ContactResource contact : contactResources) { + List contacts, + List registrars, + DateTime now) { + + // Determine what output data type to use, depending on whether more than one entity will be + // returned. + int numEntities = contacts.size(); + OutputDataType outputDataType; + // If there's more than one contact, then we know already that we need SUMMARY mode. + if (numEntities > 1) { + outputDataType = OutputDataType.SUMMARY; + // If there are fewer than two contacts, loop through and compute the total number of contacts + // and registrars, stopping as soon as we find two. + } else { + outputDataType = OutputDataType.FULL; + for (Registrar registrar : registrars) { + if (registrar.isActiveAndPubliclyVisible()) { + numEntities++; + if (numEntities > 1) { + outputDataType = OutputDataType.SUMMARY; + break; + } + } + } + } + + List> jsonOutputList = new ArrayList<>(); + // In theory, there could be more results than our max size, so limit the size. + for (ContactResource contact : contacts) { + if (jsonOutputList.size() >= rdapResultSetMaxSize) { + break; + } // As per Andy Newton on the regext mailing list, contacts by themselves have no role, since // they are global, and might have different roles for different domains. - builder.add(RdapJsonFormatter.makeRdapJsonForContact( + jsonOutputList.add(RdapJsonFormatter.makeRdapJsonForContact( contact, false, Optional.absent(), rdapLinkBase, - rdapWhoisServer, now)); + rdapWhoisServer, + now, + outputDataType)); } for (Registrar registrar : registrars) { + if (jsonOutputList.size() >= rdapResultSetMaxSize) { + break; + } if (registrar.isActiveAndPubliclyVisible()) { - builder.add(RdapJsonFormatter.makeRdapJsonForRegistrar( - registrar, false, rdapLinkBase, rdapWhoisServer, now)); + jsonOutputList.add(RdapJsonFormatter.makeRdapJsonForRegistrar( + registrar, false, rdapLinkBase, rdapWhoisServer, now, outputDataType)); } } - // In theory, there could be more results than our max size, so limit the size. - ImmutableList> resultSet = builder.build(); - return (resultSet.size() <= rdapResultSetMaxSize) - ? resultSet - : resultSet.subList(0, rdapResultSetMaxSize); + return ImmutableList.copyOf(jsonOutputList); } } diff --git a/java/google/registry/rdap/RdapHelpAction.java b/java/google/registry/rdap/RdapHelpAction.java index 70d2ed632..dbb0df80f 100644 --- a/java/google/registry/rdap/RdapHelpAction.java +++ b/java/google/registry/rdap/RdapHelpAction.java @@ -22,7 +22,6 @@ import com.google.common.collect.ImmutableMap; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapJsonFormatter.MakeRdapJsonNoticeParameters; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.InternalServerErrorException; import google.registry.request.HttpException.NotFoundException; import google.registry.util.Clock; @@ -130,7 +129,7 @@ public class RdapHelpAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { // We rely on addTopLevelEntries to notice if we are sending the TOS notice, and not add a // duplicate boilerplate entry. ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); @@ -138,6 +137,7 @@ public class RdapHelpAction extends RdapActionBase { builder, BoilerplateType.OTHER, ImmutableList.of(getJsonHelpNotice(pathSearchString, rdapLinkBase)), + ImmutableList.of(), rdapLinkBase); return builder.build(); } diff --git a/java/google/registry/rdap/RdapIcannStandardInformation.java b/java/google/registry/rdap/RdapIcannStandardInformation.java index 6101ab2a5..7250285b7 100644 --- a/java/google/registry/rdap/RdapIcannStandardInformation.java +++ b/java/google/registry/rdap/RdapIcannStandardInformation.java @@ -71,4 +71,15 @@ public class RdapIcannStandardInformation { /** Boilerplate remarks required by nameserver and entity responses. */ static final ImmutableList> nameserverAndEntityBoilerplateRemarks = ImmutableList.of(CONFORMANCE_REMARK); + + /** Required by ICANN RDAP Profile section 1.1.18. */ + static final ImmutableMap SUMMARY_DATA_REMARK = + ImmutableMap. of( + "title", + "Incomplete Data", + "description", + ImmutableList.of( + "Summary data only. For complete data, send a specific query for the object."), + "type", + "object truncated due to unexplainable reasons"); } diff --git a/java/google/registry/rdap/RdapIpAction.java b/java/google/registry/rdap/RdapIpAction.java index 0e4293f3c..63477bcec 100644 --- a/java/google/registry/rdap/RdapIpAction.java +++ b/java/google/registry/rdap/RdapIpAction.java @@ -19,7 +19,6 @@ import static google.registry.request.Action.Method.HEAD; import com.google.common.collect.ImmutableMap; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.NotImplementedException; import javax.inject.Inject; @@ -49,7 +48,7 @@ public class RdapIpAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { throw new NotImplementedException("Domain Name Registry information only"); } } diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index 01191304c..e961618e1 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -48,6 +48,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; +import java.util.List; import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; @@ -66,6 +67,19 @@ import org.joda.time.DateTime; */ public class RdapJsonFormatter { + /** + * What type of data to generate. Summary data includes only information about the object itself, + * while full data includes associated items (e.g. for domains, full data includes the hosts, + * contacts and history entries connected with the domain). Summary data is appropriate for search + * queries which return many results, to avoid load on the system. According to the ICANN + * operational profile, a remark must be attached to the returned object indicating that it + * includes only summary data. + */ + public enum OutputDataType { + FULL, + SUMMARY + } + /** * Indication of what type of boilerplate notices are required for the RDAP JSON messages. The * ICANN RDAP Profile specifies that, for instance, domain name responses should include a remark @@ -269,24 +283,27 @@ public class RdapJsonFormatter { * 1.5.20. Note that this method will only work if there are no object-specific remarks already in * the JSON object being built. If there are, the boilerplate must be merged in. * - * @param builder a builder for a JSON map object + * @param jsonBuilder a builder for a JSON map object * @param boilerplateType type of boilerplate to be added; the ICANN RDAP Profile document * mandates extra boilerplate for domain objects * @param notices a list of notices to be inserted before the boilerplate notices. If the TOS * notice is in this list, the method avoids adding a second copy. + * @param remarks a list of remarks to be inserted before the boilerplate notices. * @param rdapLinkBase the base for link URLs */ static void addTopLevelEntries( - ImmutableMap.Builder builder, + ImmutableMap.Builder jsonBuilder, BoilerplateType boilerplateType, - @Nullable Iterable> notices, + List> notices, + List> remarks, String rdapLinkBase) { - builder.put("rdapConformance", CONFORMANCE_LIST); - ImmutableList.Builder> noticesBuilder = ImmutableList.builder(); + jsonBuilder.put("rdapConformance", CONFORMANCE_LIST); + ImmutableList.Builder> noticesBuilder = + new ImmutableList.Builder<>(); ImmutableMap tosNotice = RdapHelpAction.getJsonHelpNotice(RdapHelpAction.TERMS_OF_SERVICE_PATH, rdapLinkBase); boolean tosNoticeFound = false; - if (notices != null) { + if (!notices.isEmpty()) { noticesBuilder.addAll(notices); for (ImmutableMap notice : notices) { if (notice.equals(tosNotice)) { @@ -298,18 +315,25 @@ public class RdapJsonFormatter { if (!tosNoticeFound) { noticesBuilder.add(tosNotice); } - builder.put(NOTICES, noticesBuilder.build()); + jsonBuilder.put(NOTICES, noticesBuilder.build()); + ImmutableList.Builder> remarksBuilder = + new ImmutableList.Builder<>(); + remarksBuilder.addAll(remarks); switch (boilerplateType) { case DOMAIN: - builder.put(REMARKS, RdapIcannStandardInformation.domainBoilerplateRemarks); + remarksBuilder.addAll(RdapIcannStandardInformation.domainBoilerplateRemarks); break; case NAMESERVER: case ENTITY: - builder.put(REMARKS, RdapIcannStandardInformation.nameserverAndEntityBoilerplateRemarks); + remarksBuilder.addAll(RdapIcannStandardInformation.nameserverAndEntityBoilerplateRemarks); break; default: // things other than domains, nameservers and entities cannot contain remarks break; } + ImmutableList> remarksToAdd = remarksBuilder.build(); + if (!remarksToAdd.isEmpty()) { + jsonBuilder.put(REMARKS, remarksToAdd); + } } /** AutoValue class to build parameters to {@link #makeRdapJsonNotice}. */ @@ -367,22 +391,22 @@ public class RdapJsonFormatter { */ static ImmutableMap makeRdapJsonNotice( MakeRdapJsonNoticeParameters parameters, @Nullable String linkBase) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); if (parameters.title() != null) { - builder.put("title", parameters.title()); + jsonBuilder.put("title", parameters.title()); } ImmutableList.Builder descriptionBuilder = new ImmutableList.Builder<>(); for (String line : parameters.description()) { descriptionBuilder.add(nullToEmpty(line)); } - builder.put("description", descriptionBuilder.build()); + jsonBuilder.put("description", descriptionBuilder.build()); if (parameters.typeString() != null) { - builder.put("typeString", parameters.typeString()); + jsonBuilder.put("typeString", parameters.typeString()); } String linkValueString = nullToEmpty(linkBase) + nullToEmpty(parameters.linkValueSuffix()); if (parameters.linkHrefUrlString() == null) { - builder.put("links", ImmutableList.of(ImmutableMap.of( + jsonBuilder.put("links", ImmutableList.of(ImmutableMap.of( "value", linkValueString, "rel", "self", "href", linkValueString, @@ -390,112 +414,146 @@ public class RdapJsonFormatter { } else { URI htmlBaseURI = URI.create(nullToEmpty(linkBase)); URI htmlUri = htmlBaseURI.resolve(parameters.linkHrefUrlString()); - builder.put("links", ImmutableList.of(ImmutableMap.of( + jsonBuilder.put("links", ImmutableList.of(ImmutableMap.of( "value", linkValueString, "rel", "alternate", "href", htmlUri.toString(), "type", "text/html"))); } - return builder.build(); + return jsonBuilder.build(); } /** * Creates a JSON object for a {@link DomainResource}. * * @param domainResource the domain resource object from which the JSON object should be created + * @param isTopLevel if true, the top-level boilerplate will be added * @param linkBase the URL base to be used when creating links * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the * port43 field; if null, port43 is not added to the object + * @param now the as-date + * @param outputDataType whether to generate full or summary data */ static ImmutableMap makeRdapJsonForDomain( DomainResource domainResource, boolean isTopLevel, @Nullable String linkBase, @Nullable String whoisServer, - DateTime now) { - // Kick off the database loads of the nameservers that we will need. - Map, HostResource> loadedHosts = - ofy().load().keys(domainResource.getNameservers()); - // And the registrant and other contacts. - Map, ContactResource> loadedContacts = - ofy().load().keys(domainResource.getReferencedContacts()); - - // Now, assemble the results, using the loaded objects as needed. - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("objectClassName", "domain"); - builder.put("handle", domainResource.getRepoId()); - builder.put("ldhName", domainResource.getFullyQualifiedDomainName()); + DateTime now, + OutputDataType outputDataType) { + // Start with the domain-level information. + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("objectClassName", "domain"); + jsonBuilder.put("handle", domainResource.getRepoId()); + jsonBuilder.put("ldhName", domainResource.getFullyQualifiedDomainName()); // Only include the unicodeName field if there are unicode characters. if (hasUnicodeComponents(domainResource.getFullyQualifiedDomainName())) { - builder.put("unicodeName", Idn.toUnicode(domainResource.getFullyQualifiedDomainName())); + jsonBuilder.put("unicodeName", Idn.toUnicode(domainResource.getFullyQualifiedDomainName())); } - builder.put("status", makeStatusValueList(domainResource.getStatusValues())); - builder.put("links", ImmutableList.of( + jsonBuilder.put("status", makeStatusValueList(domainResource.getStatusValues())); + jsonBuilder.put("links", ImmutableList.of( makeLink("domain", domainResource.getFullyQualifiedDomainName(), linkBase))); - ImmutableList events = makeEvents(domainResource, now); - if (!events.isEmpty()) { - builder.put("events", events); - } - // Nameservers - ImmutableList.Builder nsBuilder = new ImmutableList.Builder<>(); - for (HostResource hostResource - : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts.values())) { - nsBuilder.add(makeRdapJsonForHost(hostResource, false, linkBase, null, now)); - } - ImmutableList ns = nsBuilder.build(); - if (!ns.isEmpty()) { - builder.put("nameservers", ns); - } - // Contacts - ImmutableList.Builder entitiesBuilder = new ImmutableList.Builder<>(); - for (DesignatedContact designatedContact : FluentIterable.from(domainResource.getContacts()) - .append(DesignatedContact.create(Type.REGISTRANT, domainResource.getRegistrant())) - .toSortedList(DESIGNATED_CONTACT_ORDERING)) { - ContactResource loadedContact = loadedContacts.get(designatedContact.getContactKey()); - entitiesBuilder.add(makeRdapJsonForContact( - loadedContact, false, Optional.of(designatedContact.getType()), linkBase, null, now)); - } - ImmutableList entities = entitiesBuilder.build(); - if (!entities.isEmpty()) { - builder.put("entities", entities); + // If we are outputting all data (not just summary data), also add information about hosts, + // contacts and events (history entries). If we are outputting summary data, instead add a + // remark indicating that fact. + List> remarks; + if (outputDataType == OutputDataType.SUMMARY) { + remarks = ImmutableList.of(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); + } else { + remarks = ImmutableList.of(); + ImmutableList events = makeEvents(domainResource, now); + if (!events.isEmpty()) { + jsonBuilder.put("events", events); + } + // Kick off the database loads of the nameservers that we will need. + Map, HostResource> loadedHosts = + ofy().load().keys(domainResource.getNameservers()); + // And the registrant and other contacts. + Map, ContactResource> loadedContacts = + ofy().load().keys(domainResource.getReferencedContacts()); + // Nameservers + ImmutableList.Builder nsBuilder = new ImmutableList.Builder<>(); + for (HostResource hostResource + : HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts.values())) { + nsBuilder.add(makeRdapJsonForHost( + hostResource, false, linkBase, null, now, outputDataType)); + } + ImmutableList ns = nsBuilder.build(); + if (!ns.isEmpty()) { + jsonBuilder.put("nameservers", ns); + } + // Contacts + ImmutableList.Builder entitiesBuilder = new ImmutableList.Builder<>(); + for (DesignatedContact designatedContact : FluentIterable.from(domainResource.getContacts()) + .append(DesignatedContact.create(Type.REGISTRANT, domainResource.getRegistrant())) + .toSortedList(DESIGNATED_CONTACT_ORDERING)) { + ContactResource loadedContact = loadedContacts.get(designatedContact.getContactKey()); + entitiesBuilder.add(makeRdapJsonForContact( + loadedContact, + false, + Optional.of(designatedContact.getType()), + linkBase, + null, + now, + outputDataType)); + } + ImmutableList entities = entitiesBuilder.build(); + if (!entities.isEmpty()) { + jsonBuilder.put("entities", entities); + } } if (whoisServer != null) { - builder.put("port43", whoisServer); + jsonBuilder.put("port43", whoisServer); } if (isTopLevel) { - addTopLevelEntries(builder, BoilerplateType.DOMAIN, null, linkBase); + addTopLevelEntries( + jsonBuilder, BoilerplateType.DOMAIN, remarks, ImmutableList.of(), linkBase); + } else if (!remarks.isEmpty()) { + jsonBuilder.put(REMARKS, remarks); } - return builder.build(); + return jsonBuilder.build(); } /** * Creates a JSON object for a {@link HostResource}. * * @param hostResource the host resource object from which the JSON object should be created + * @param isTopLevel if true, the top-level boilerplate will be added * @param linkBase the URL base to be used when creating links * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the * port43 field; if null, port43 is not added to the object + * @param now the as-date + * @param outputDataType whether to generate full or summary data */ static ImmutableMap makeRdapJsonForHost( HostResource hostResource, boolean isTopLevel, @Nullable String linkBase, @Nullable String whoisServer, - DateTime now) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("objectClassName", "nameserver"); - builder.put("handle", hostResource.getRepoId()); - builder.put("ldhName", hostResource.getFullyQualifiedHostName()); + DateTime now, + OutputDataType outputDataType) { + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("objectClassName", "nameserver"); + jsonBuilder.put("handle", hostResource.getRepoId()); + jsonBuilder.put("ldhName", hostResource.getFullyQualifiedHostName()); // Only include the unicodeName field if there are unicode characters. if (hasUnicodeComponents(hostResource.getFullyQualifiedHostName())) { - builder.put("unicodeName", Idn.toUnicode(hostResource.getFullyQualifiedHostName())); + jsonBuilder.put("unicodeName", Idn.toUnicode(hostResource.getFullyQualifiedHostName())); } - builder.put("status", makeStatusValueList(hostResource.getStatusValues())); - builder.put("links", ImmutableList.of( + jsonBuilder.put("status", makeStatusValueList(hostResource.getStatusValues())); + jsonBuilder.put("links", ImmutableList.of( makeLink("nameserver", hostResource.getFullyQualifiedHostName(), linkBase))); - ImmutableList events = makeEvents(hostResource, now); - if (!events.isEmpty()) { - builder.put("events", events); + List> remarks; + // If we are outputting all data (not just summary data), also add events taken from the history + // entries. If we are outputting summary data, instead add a remark indicating that fact. + if (outputDataType == OutputDataType.SUMMARY) { + remarks = ImmutableList.of(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); + } else { + remarks = ImmutableList.of(); + ImmutableList events = makeEvents(hostResource, now); + if (!events.isEmpty()) { + jsonBuilder.put("events", events); + } } ImmutableSet inetAddresses = hostResource.getInetAddresses(); if (!inetAddresses.isEmpty()) { @@ -520,26 +578,32 @@ public class RdapJsonFormatter { } ImmutableMap> ipAddresses = ipAddressesBuilder.build(); if (!ipAddresses.isEmpty()) { - builder.put("ipAddresses", ipAddressesBuilder.build()); + jsonBuilder.put("ipAddresses", ipAddressesBuilder.build()); } } if (whoisServer != null) { - builder.put("port43", whoisServer); + jsonBuilder.put("port43", whoisServer); } if (isTopLevel) { - addTopLevelEntries(builder, BoilerplateType.NAMESERVER, null, linkBase); + addTopLevelEntries( + jsonBuilder, BoilerplateType.NAMESERVER, remarks, ImmutableList.of(), linkBase); + } else if (!remarks.isEmpty()) { + jsonBuilder.put(REMARKS, remarks); } - return builder.build(); + return jsonBuilder.build(); } /** * Creates a JSON object for a {@link ContactResource} and associated contact type. * * @param contactResource the contact resource object from which the JSON object should be created + * @param isTopLevel if true, the top-level boilerplate will be added * @param contactType the contact type to map to an RDAP role; if absent, no role is listed * @param linkBase the URL base to be used when creating links * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the * port43 field; if null, port43 is not added to the object + * @param now the as-date + * @param outputDataType whether to generate full or summary data */ static ImmutableMap makeRdapJsonForContact( ContactResource contactResource, @@ -547,15 +611,17 @@ public class RdapJsonFormatter { Optional contactType, @Nullable String linkBase, @Nullable String whoisServer, - DateTime now) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("objectClassName", "entity"); - builder.put("handle", contactResource.getRepoId()); - builder.put("status", makeStatusValueList(contactResource.getStatusValues())); + DateTime now, + OutputDataType outputDataType) { + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("objectClassName", "entity"); + jsonBuilder.put("handle", contactResource.getRepoId()); + jsonBuilder.put("status", makeStatusValueList(contactResource.getStatusValues())); if (contactType.isPresent()) { - builder.put("roles", ImmutableList.of(convertContactTypeToRdapRole(contactType.get()))); + jsonBuilder.put("roles", + ImmutableList.of(convertContactTypeToRdapRole(contactType.get()))); } - builder.put("links", + jsonBuilder.put("links", ImmutableList.of(makeLink("entity", contactResource.getRepoId(), linkBase))); // Create the vCard. ImmutableList.Builder vcardBuilder = new ImmutableList.Builder<>(); @@ -588,42 +654,57 @@ public class RdapJsonFormatter { if (emailAddress != null) { vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress)); } - builder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build())); - ImmutableList events = makeEvents(contactResource, now); - if (!events.isEmpty()) { - builder.put("events", events); + jsonBuilder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build())); + // If we are outputting all data (not just summary data), also add events taken from the history + // entries. If we are outputting summary data, instead add a remark indicating that fact. + List> remarks; + if (outputDataType == OutputDataType.SUMMARY) { + remarks = ImmutableList.of(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); + } else { + remarks = ImmutableList.of(); + ImmutableList events = makeEvents(contactResource, now); + if (!events.isEmpty()) { + jsonBuilder.put("events", events); + } } if (whoisServer != null) { - builder.put("port43", whoisServer); + jsonBuilder.put("port43", whoisServer); } if (isTopLevel) { - addTopLevelEntries(builder, BoilerplateType.ENTITY, null, linkBase); + addTopLevelEntries( + jsonBuilder, BoilerplateType.ENTITY, remarks, ImmutableList.of(), linkBase); + } else if (!remarks.isEmpty()) { + jsonBuilder.put(REMARKS, remarks); } - return builder.build(); + return jsonBuilder.build(); } /** * Creates a JSON object for a {@link Registrar}. * * @param registrar the registrar object from which the JSON object should be created + * @param isTopLevel if true, the top-level boilerplate will be added * @param linkBase the URL base to be used when creating links * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the * port43 field; if null, port43 is not added to the object + * @param now the as-date + * @param outputDataType whether to generate full or summary data */ static ImmutableMap makeRdapJsonForRegistrar( Registrar registrar, boolean isTopLevel, @Nullable String linkBase, @Nullable String whoisServer, - DateTime now) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("objectClassName", "entity"); - builder.put("handle", registrar.getIanaIdentifier().toString()); - builder.put("status", STATUS_LIST_ACTIVE); - builder.put("roles", ImmutableList.of(RdapEntityRole.REGISTRAR.rfc7483String)); - builder.put("links", + DateTime now, + OutputDataType outputDataType) { + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("objectClassName", "entity"); + jsonBuilder.put("handle", registrar.getIanaIdentifier().toString()); + jsonBuilder.put("status", STATUS_LIST_ACTIVE); + jsonBuilder.put("roles", ImmutableList.of(RdapEntityRole.REGISTRAR.rfc7483String)); + jsonBuilder.put("links", ImmutableList.of(makeLink("entity", registrar.getIanaIdentifier().toString(), linkBase))); - builder.put("publicIds", + jsonBuilder.put("publicIds", ImmutableList.of( ImmutableMap.of( "type", "IANA Registrar ID", @@ -657,30 +738,41 @@ public class RdapJsonFormatter { if (emailAddress != null) { vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress)); } - builder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build())); - ImmutableList events = makeEvents(registrar, now); - if (!events.isEmpty()) { - builder.put("events", events); - } - // include the registrar contacts as subentities - ImmutableList.Builder> registrarContactsBuilder = - new ImmutableList.Builder<>(); - for (RegistrarContact registrarContact : registrar.getContacts()) { - if (isVisible(registrarContact)) { - registrarContactsBuilder.add(makeRdapJsonForRegistrarContact(registrarContact, null)); + jsonBuilder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build())); + // If we are outputting all data (not just summary data), also add registrar contacts. If we are + // outputting summary data, instead add a remark indicating that fact. + List> remarks; + if (outputDataType == OutputDataType.SUMMARY) { + remarks = ImmutableList.of(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); + } else { + remarks = ImmutableList.of(); + ImmutableList events = makeEvents(registrar, now); + if (!events.isEmpty()) { + jsonBuilder.put("events", events); + } + // include the registrar contacts as subentities + ImmutableList.Builder> registrarContactsBuilder = + new ImmutableList.Builder<>(); + for (RegistrarContact registrarContact : registrar.getContacts()) { + if (isVisible(registrarContact)) { + registrarContactsBuilder.add(makeRdapJsonForRegistrarContact(registrarContact, null)); + } + } + ImmutableList> registrarContacts = registrarContactsBuilder.build(); + if (!registrarContacts.isEmpty()) { + jsonBuilder.put("entities", registrarContacts); } } - ImmutableList> registrarContacts = registrarContactsBuilder.build(); - if (!registrarContacts.isEmpty()) { - builder.put("entities", registrarContacts); - } if (whoisServer != null) { - builder.put("port43", whoisServer); + jsonBuilder.put("port43", whoisServer); } if (isTopLevel) { - addTopLevelEntries(builder, BoilerplateType.ENTITY, null, linkBase); + addTopLevelEntries( + jsonBuilder, BoilerplateType.ENTITY, remarks, ImmutableList.of(), linkBase); + } else if (!remarks.isEmpty()) { + jsonBuilder.put(REMARKS, remarks); } - return builder.build(); + return jsonBuilder.build(); } /** @@ -692,14 +784,14 @@ public class RdapJsonFormatter { */ static ImmutableMap makeRdapJsonForRegistrarContact( RegistrarContact registrarContact, @Nullable String whoisServer) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("objectClassName", "entity"); + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("objectClassName", "entity"); String gaeUserId = registrarContact.getGaeUserId(); if (gaeUserId != null) { - builder.put("handle", registrarContact.getGaeUserId()); + jsonBuilder.put("handle", registrarContact.getGaeUserId()); } - builder.put("status", STATUS_LIST_ACTIVE); - builder.put("roles", makeRdapRoleList(registrarContact)); + jsonBuilder.put("status", STATUS_LIST_ACTIVE); + jsonBuilder.put("roles", makeRdapRoleList(registrarContact)); // Create the vCard. ImmutableList.Builder vcardBuilder = new ImmutableList.Builder<>(); vcardBuilder.add(VCARD_ENTRY_VERSION); @@ -719,11 +811,11 @@ public class RdapJsonFormatter { if (emailAddress != null) { vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress)); } - builder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build())); + jsonBuilder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build())); if (whoisServer != null) { - builder.put("port43", whoisServer); + jsonBuilder.put("port43", whoisServer); } - return builder.build(); + return jsonBuilder.build(); } /** Converts a domain registry contact type into a role as defined by RFC 7483. */ @@ -818,13 +910,13 @@ public class RdapJsonFormatter { */ private static ImmutableMap makeEvent( RdapEventAction eventAction, @Nullable String eventActor, DateTime eventDate) { - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("eventAction", eventAction.getDisplayName()); + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("eventAction", eventAction.getDisplayName()); if (eventActor != null) { - builder.put("eventActor", eventActor); + jsonBuilder.put("eventActor", eventActor); } - builder.put("eventDate", eventDate.toString()); - return builder.build(); + jsonBuilder.put("eventDate", eventDate.toString()); + return jsonBuilder.build(); } /** @@ -837,9 +929,9 @@ public class RdapJsonFormatter { if (address == null) { return null; } - ImmutableList.Builder builder = new ImmutableList.Builder<>(); - builder.add(""); // PO box - builder.add(""); // extended address + ImmutableList.Builder jsonBuilder = new ImmutableList.Builder<>(); + jsonBuilder.add(""); // PO box + jsonBuilder.add(""); // extended address // The vCard spec allows several different ways to handle multiline street addresses. Per // Gustavo Lozano of ICANN, the one we should use is an embedded array of street address lines @@ -859,21 +951,21 @@ public class RdapJsonFormatter { // ] ImmutableList street = address.getStreet(); if (street.isEmpty()) { - builder.add(""); + jsonBuilder.add(""); } else if (street.size() == 1) { - builder.add(street.get(0)); + jsonBuilder.add(street.get(0)); } else { - builder.add(street); + jsonBuilder.add(street); } - builder.add(nullToEmpty(address.getCity())); - builder.add(nullToEmpty(address.getState())); - builder.add(nullToEmpty(address.getZip())); - builder.add(new Locale("en", address.getCountryCode()).getDisplayCountry(new Locale("en"))); + jsonBuilder.add(nullToEmpty(address.getCity())); + jsonBuilder.add(nullToEmpty(address.getState())); + jsonBuilder.add(nullToEmpty(address.getZip())); + jsonBuilder.add(new Locale("en", address.getCountryCode()).getDisplayCountry(new Locale("en"))); return ImmutableList.of( "adr", ImmutableMap.of(), "text", - builder.build()); + jsonBuilder.build()); } /** Creates a vCard phone number entry. */ diff --git a/java/google/registry/rdap/RdapNameserverAction.java b/java/google/registry/rdap/RdapNameserverAction.java index 0f91d8a19..92870aa87 100644 --- a/java/google/registry/rdap/RdapNameserverAction.java +++ b/java/google/registry/rdap/RdapNameserverAction.java @@ -20,8 +20,8 @@ import static google.registry.request.Action.Method.HEAD; import com.google.common.collect.ImmutableMap; import google.registry.model.host.HostResource; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.NotFoundException; import google.registry.util.Clock; import javax.inject.Inject; @@ -50,7 +50,7 @@ public class RdapNameserverAction extends RdapActionBase { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { DateTime now = clock.nowUtc(); pathSearchString = canonicalizeName(pathSearchString); // The RDAP syntax is /rdap/nameserver/ns1.mydomain.com. @@ -61,6 +61,6 @@ public class RdapNameserverAction extends RdapActionBase { throw new NotFoundException(pathSearchString + " not found"); } return RdapJsonFormatter.makeRdapJsonForHost( - hostResource, true, rdapLinkBase, rdapWhoisServer, now); + hostResource, true, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL); } } diff --git a/java/google/registry/rdap/RdapNameserverSearchAction.java b/java/google/registry/rdap/RdapNameserverSearchAction.java index d644e6c20..263d13a47 100644 --- a/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -25,19 +25,19 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.primitives.Booleans; -import com.googlecode.objectify.cmd.Query; import google.registry.config.ConfigModule.Config; import google.registry.model.domain.DomainResource; import google.registry.model.host.HostResource; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; -import google.registry.request.HttpException; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; import google.registry.request.Parameter; import google.registry.util.Clock; import google.registry.util.Idn; import java.net.InetAddress; +import java.util.List; import javax.inject.Inject; import org.joda.time.DateTime; @@ -75,7 +75,7 @@ public class RdapNameserverSearchAction extends RdapActionBase { /** Parses the parameters and calls the appropriate search function. */ @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { DateTime now = clock.nowUtc(); // RDAP syntax example: /rdap/nameservers?name=ns*.example.com. // The pathSearchString is not used by search commands. @@ -101,16 +101,20 @@ public class RdapNameserverSearchAction extends RdapActionBase { if (results.isEmpty()) { throw new NotFoundException("No nameservers found"); } - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - builder.put("nameserverSearchResults", results); - RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.NAMESERVER, null, rdapLinkBase); - return builder.build(); + ImmutableMap.Builder jsonBuilder = new ImmutableMap.Builder<>(); + jsonBuilder.put("nameserverSearchResults", results); + RdapJsonFormatter.addTopLevelEntries( + jsonBuilder, + BoilerplateType.NAMESERVER, + ImmutableList.of(), + ImmutableList.of(), + rdapLinkBase); + return jsonBuilder.build(); } /** Searches for nameservers by name, returning a JSON array of nameserver info maps. */ private ImmutableList> - searchByName(final RdapSearchPattern partialStringQuery, final DateTime now) - throws HttpException { + searchByName(final RdapSearchPattern partialStringQuery, final DateTime now) { // Handle queries without a wildcard -- just load by foreign key. if (!partialStringQuery.getHasWildcard()) { HostResource hostResource = @@ -120,21 +124,18 @@ public class RdapNameserverSearchAction extends RdapActionBase { } return ImmutableList.of( RdapJsonFormatter.makeRdapJsonForHost( - hostResource, false, rdapLinkBase, rdapWhoisServer, now)); + hostResource, false, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL)); // Handle queries with a wildcard, but no suffix. There are no pending deletes for hosts, so we // can call queryUndeleted. } else if (partialStringQuery.getSuffix() == null) { - Query query = queryUndeleted( - HostResource.class, - "fullyQualifiedHostName", - partialStringQuery, rdapResultSetMaxSize); - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); - for (HostResource hostResource : query) { - builder.add( - RdapJsonFormatter.makeRdapJsonForHost( - hostResource, false, rdapLinkBase, rdapWhoisServer, now)); - } - return builder.build(); + return makeSearchResults( + queryUndeleted( + HostResource.class, + "fullyQualifiedHostName", + partialStringQuery, + rdapResultSetMaxSize) + .list(), + now); // Handle queries with a wildcard and a suffix. In this case, it is more efficient to do things // differently. We use the suffix to look up the domain, then loop through the subordinate hosts // looking for matches. @@ -144,41 +145,45 @@ public class RdapNameserverSearchAction extends RdapActionBase { if (domainResource == null) { throw new NotFoundException("No domain found for specified nameserver suffix"); } - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); + ImmutableList.Builder hostListBuilder = new ImmutableList.Builder<>(); for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) { // We can't just check that the host name starts with the initial query string, because then // the query ns.exam*.example.com would match against nameserver ns.example.com. if (partialStringQuery.matches(fqhn)) { HostResource hostResource = loadByUniqueId(HostResource.class, fqhn, now); if (hostResource != null) { - builder.add( - RdapJsonFormatter.makeRdapJsonForHost( - hostResource, false, rdapLinkBase, rdapWhoisServer, now)); + hostListBuilder.add(hostResource); } } } - return builder.build(); + return makeSearchResults(hostListBuilder.build(), now); } } /** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */ private ImmutableList> - searchByIp(final InetAddress inetAddress, DateTime now) throws HttpException { - // In theory, we could filter on deletion time being in the future. But we can't do that in the - // name query above (because we already have an inequality filter), and filtering on deletion - // time differently in the two cases seems like a recipe for future confusion. - Query query = ofy() - .load() - .type(HostResource.class) - .filter("inetAddresses", inetAddress.getHostAddress()) - .filter("deletionTime", END_OF_TIME) - .limit(rdapResultSetMaxSize); - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); - for (HostResource hostResource : query) { - builder.add( + searchByIp(final InetAddress inetAddress, DateTime now) { + return makeSearchResults( + ofy().load() + .type(HostResource.class) + .filter("inetAddresses", inetAddress.getHostAddress()) + .filter("deletionTime", END_OF_TIME) + .limit(rdapResultSetMaxSize) + .list(), + now); + } + + /** Output JSON for a list of hosts. */ + private ImmutableList> makeSearchResults( + List hosts, DateTime now) { + OutputDataType outputDataType = + (hosts.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; + ImmutableList.Builder> jsonBuilder = new ImmutableList.Builder<>(); + for (HostResource host : hosts) { + jsonBuilder.add( RdapJsonFormatter.makeRdapJsonForHost( - hostResource, false, rdapLinkBase, rdapWhoisServer, now)); + host, false, rdapLinkBase, rdapWhoisServer, now, outputDataType)); } - return builder.build(); + return jsonBuilder.build(); } } diff --git a/javatests/google/registry/rdap/RdapActionBaseTest.java b/javatests/google/registry/rdap/RdapActionBaseTest.java index b391ab040..4ac607f6e 100644 --- a/javatests/google/registry/rdap/RdapActionBaseTest.java +++ b/javatests/google/registry/rdap/RdapActionBaseTest.java @@ -21,10 +21,10 @@ import static google.registry.request.Action.Method.HEAD; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.TestDataHelper.loadFileWithSubstitutions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import google.registry.model.ofy.Ofy; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; -import google.registry.request.HttpException; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; @@ -55,7 +55,7 @@ public class RdapActionBaseTest { /** * Dummy RdapActionBase subclass used for testing. */ - class RdapTestAction extends RdapActionBase { + static class RdapTestAction extends RdapActionBase { public static final String PATH = "/rdap/test/"; @@ -71,7 +71,7 @@ public class RdapActionBaseTest { @Override public ImmutableMap getJsonObjectForResource( - String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException { + String pathSearchString, boolean isHeadRequest, String linkBase) { if (pathSearchString.equals("IllegalArgumentException")) { throw new IllegalArgumentException(); } @@ -83,7 +83,8 @@ public class RdapActionBaseTest { RdapJsonFormatter.addTopLevelEntries( builder, BoilerplateType.OTHER, - null, + ImmutableList.of(), + ImmutableList.of(), "http://myserver.google.com/"); return builder.build(); } diff --git a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java index 4708ddd9e..9f9fc1fec 100644 --- a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistDomainAsDeleted; import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.DatastoreHelper.persistResources; import static google.registry.testing.DatastoreHelper.persistSimpleResources; import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistContactResource; import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistHostResource; @@ -110,7 +111,7 @@ public class RdapDomainSearchActionTest { action.nsIpParam = Optional.absent(); break; } - action.rdapResultSetMaxSize = 5; + action.rdapResultSetMaxSize = 4; action.run(); return JSONValue.parse(response.getPayload()); } @@ -518,20 +519,21 @@ public class RdapDomainSearchActionTest { } private void createManyDomains(int numActiveDomains, int numTotalDomainsPerActiveDomain) { + // Create all the domains at once, then persist them in parallel, for increased efficiency. + ImmutableList.Builder domainsBuilder = new ImmutableList.Builder<>(); for (int i = 1; i <= numActiveDomains * numTotalDomainsPerActiveDomain; i++) { String domainName = String.format("domain%d.lol", i); - DomainResource domain = + DomainResource.Builder builder = makeDomainResource( domainName, contact1, contact2, contact3, hostNs1CatLol, hostNs2CatLol, registrar) .asBuilder() - .setCreationTimeForTest(clock.nowUtc().minusYears(3)) - .build(); - if (i % numTotalDomainsPerActiveDomain == 0) { - persistResource(domain); - } else { - persistDomainAsDeleted(domain, clock.nowUtc()); + .setCreationTimeForTest(clock.nowUtc().minusYears(3)); + if (i % numTotalDomainsPerActiveDomain != 0) { + builder = builder.setDeletionTime(clock.nowUtc().minusDays(1)); } + domainsBuilder.add(builder.build()); } + persistResources(domainsBuilder.build()); } private void checkNumberOfDomainsInResult(Object obj, int expected) { @@ -549,10 +551,10 @@ public class RdapDomainSearchActionTest { @Test public void testDomainMatch_manyDeletedDomains_fullResultSet() throws Exception { // There are enough domains to fill a full result set; deleted domains are ignored. - createManyDomains(5, 4); + createManyDomains(4, 4); Object obj = generateActualJson(RequestType.NAME, "domain*.lol"); assertThat(response.getStatus()).isEqualTo(200); - checkNumberOfDomainsInResult(obj, 5); + checkNumberOfDomainsInResult(obj, 4); } @Test @@ -571,7 +573,7 @@ public class RdapDomainSearchActionTest { // This is not exactly desired behavior, but expected: There are enough domains to fill a full // result set, but there are so many deleted domains that we run out of patience before we work // our way through all of them. - createManyDomains(5, 150); + createManyDomains(4, 150); Object obj = generateActualJson(RequestType.NAME, "domain*.lol"); assertThat(response.getStatus()).isEqualTo(200); checkNumberOfDomainsInResult(obj, 3); @@ -587,7 +589,8 @@ public class RdapDomainSearchActionTest { @Test public void testNameserverMatchWithWildcard_found() throws Exception { assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns2.cat.l*")) - .isEqualTo(generateExpectedJsonForDomain("cat.lol", null, "C-LOL", "rdap_domain.json")); + .isEqualTo( + generateExpectedJsonForDomain("cat.lol", null, "C-LOL", "rdap_domain.json")); assertThat(response.getStatus()).isEqualTo(200); } @@ -672,7 +675,8 @@ public class RdapDomainSearchActionTest { public void testNameserverMatchOneDeletedDomain_foundTheOther() throws Exception { persistDomainAsDeleted(domainCatExample, clock.nowUtc()); assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns1.cat.lol")) - .isEqualTo(generateExpectedJsonForDomain("cat.lol", null, "C-LOL", "rdap_domain.json")); + .isEqualTo( + generateExpectedJsonForDomain("cat.lol", null, "C-LOL", "rdap_domain.json")); assertThat(response.getStatus()).isEqualTo(200); } @@ -748,7 +752,8 @@ public class RdapDomainSearchActionTest { public void testAddressMatchOneDeletedDomain_foundTheOther() throws Exception { persistDomainAsDeleted(domainCatExample, clock.nowUtc()); assertThat(generateActualJson(RequestType.NS_IP, "1.2.3.4")) - .isEqualTo(generateExpectedJsonForDomain("cat.lol", null, "C-LOL", "rdap_domain.json")); + .isEqualTo( + generateExpectedJsonForDomain("cat.lol", null, "C-LOL", "rdap_domain.json")); assertThat(response.getStatus()).isEqualTo(200); } diff --git a/javatests/google/registry/rdap/RdapJsonFormatterTest.java b/javatests/google/registry/rdap/RdapJsonFormatterTest.java index 3d80a0e1f..7fa6f7629 100644 --- a/javatests/google/registry/rdap/RdapJsonFormatterTest.java +++ b/javatests/google/registry/rdap/RdapJsonFormatterTest.java @@ -41,6 +41,7 @@ import google.registry.model.registrar.RegistrarContact; import google.registry.model.registry.Registry.TldState; import google.registry.model.reporting.HistoryEntry; import google.registry.rdap.RdapJsonFormatter.MakeRdapJsonNoticeParameters; +import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.InjectRule; @@ -204,35 +205,59 @@ public class RdapJsonFormatterTest { @Test public void testRegistrar() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForRegistrar( - registrar, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + registrar, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_registrar.json")); } + @Test + public void testRegistrar_summary() throws Exception { + assertThat(RdapJsonFormatter.makeRdapJsonForRegistrar( + registrar, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.SUMMARY)) + .isEqualTo(loadJson("rdapjson_registrar_summary.json")); + } + @Test public void testHost_ipv4() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForHost( - hostResourceIpv4, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + hostResourceIpv4, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_host_ipv4.json")); } @Test public void testHost_ipv6() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForHost( - hostResourceIpv6, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + hostResourceIpv6, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_host_ipv6.json")); } @Test public void testHost_both() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForHost( - hostResourceBoth, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + hostResourceBoth, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc(), OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_host_both.json")); } + @Test + public void testHost_both_summary() throws Exception { + assertThat(RdapJsonFormatter.makeRdapJsonForHost( + hostResourceBoth, + false, + LINK_BASE, + WHOIS_SERVER, + clock.nowUtc(), + OutputDataType.SUMMARY)) + .isEqualTo(loadJson("rdapjson_host_both_summary.json")); + } + @Test public void testHost_noAddresses() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForHost( - hostResourceNoAddresses, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + hostResourceNoAddresses, + false, + LINK_BASE, + WHOIS_SERVER, + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_host_no_addresses.json")); } @@ -245,10 +270,25 @@ public class RdapJsonFormatterTest { Optional.of(DesignatedContact.Type.REGISTRANT), LINK_BASE, WHOIS_SERVER, - clock.nowUtc())) + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_registrant.json")); } + @Test + public void testRegistrant_summary() throws Exception { + assertThat( + RdapJsonFormatter.makeRdapJsonForContact( + contactResourceRegistrant, + false, + Optional.of(DesignatedContact.Type.REGISTRANT), + LINK_BASE, + WHOIS_SERVER, + clock.nowUtc(), + OutputDataType.SUMMARY)) + .isEqualTo(loadJson("rdapjson_registrant_summary.json")); + } + @Test public void testRegistrant_baseHasNoTrailingSlash() throws Exception { assertThat( @@ -258,7 +298,8 @@ public class RdapJsonFormatterTest { Optional.of(DesignatedContact.Type.REGISTRANT), LINK_BASE_NO_TRAILING_SLASH, WHOIS_SERVER, - clock.nowUtc())) + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_registrant.json")); } @@ -271,7 +312,8 @@ public class RdapJsonFormatterTest { Optional.of(DesignatedContact.Type.REGISTRANT), null, WHOIS_SERVER, - clock.nowUtc())) + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_registrant_nobase.json")); } @@ -284,7 +326,8 @@ public class RdapJsonFormatterTest { Optional.of(DesignatedContact.Type.ADMIN), LINK_BASE, WHOIS_SERVER, - clock.nowUtc())) + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_admincontact.json")); } @@ -297,7 +340,8 @@ public class RdapJsonFormatterTest { Optional.of(DesignatedContact.Type.TECH), LINK_BASE, WHOIS_SERVER, - clock.nowUtc())) + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_techcontact.json")); } @@ -310,21 +354,44 @@ public class RdapJsonFormatterTest { Optional.absent(), LINK_BASE, WHOIS_SERVER, - clock.nowUtc())) + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_rolelesscontact.json")); } @Test public void testDomain_full() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForDomain( - domainResourceFull, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + domainResourceFull, + false, + LINK_BASE, + WHOIS_SERVER, + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_domain_full.json")); } + @Test + public void testDomain_summary() throws Exception { + assertThat(RdapJsonFormatter.makeRdapJsonForDomain( + domainResourceFull, + false, + LINK_BASE, + WHOIS_SERVER, + clock.nowUtc(), + OutputDataType.SUMMARY)) + .isEqualTo(loadJson("rdapjson_domain_summary.json")); + } + @Test public void testDomain_noNameservers() throws Exception { assertThat(RdapJsonFormatter.makeRdapJsonForDomain( - domainResourceNoNameservers, false, LINK_BASE, WHOIS_SERVER, clock.nowUtc())) + domainResourceNoNameservers, + false, + LINK_BASE, + WHOIS_SERVER, + clock.nowUtc(), + OutputDataType.FULL)) .isEqualTo(loadJson("rdapjson_domain_no_nameservers.json")); } @@ -406,7 +473,8 @@ public class RdapJsonFormatterTest { RdapJsonFormatter.addTopLevelEntries( builder, RdapJsonFormatter.BoilerplateType.OTHER, - null, + ImmutableList.of(), + ImmutableList.of(), LINK_BASE); assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel.json")); } @@ -419,6 +487,7 @@ public class RdapJsonFormatterTest { builder, RdapJsonFormatter.BoilerplateType.OTHER, ImmutableList.of(RdapHelpAction.getJsonHelpNotice("/tos", LINK_BASE)), + ImmutableList.of(), LINK_BASE); assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel.json")); } @@ -430,7 +499,8 @@ public class RdapJsonFormatterTest { RdapJsonFormatter.addTopLevelEntries( builder, RdapJsonFormatter.BoilerplateType.DOMAIN, - null, + ImmutableList.of(), + ImmutableList.of(), LINK_BASE); assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel_domain.json")); } @@ -443,6 +513,7 @@ public class RdapJsonFormatterTest { builder, RdapJsonFormatter.BoilerplateType.DOMAIN, ImmutableList.of(RdapHelpAction.getJsonHelpNotice("/tos", LINK_BASE)), + ImmutableList.of(), LINK_BASE); assertThat(builder.build()).isEqualTo(loadJson("rdapjson_toplevel_domain.json")); } diff --git a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java index 895468d62..50a362acd 100644 --- a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java @@ -382,7 +382,12 @@ public class RdapNameserverSearchActionTest { assertThat(generateActualJsonWithIp("bad:f00d:cafe::15:beef")) .isEqualTo( generateExpectedJsonForNameserver( - "ns2.cat.lol", null, "4-ROID", "v6", "bad:f00d:cafe::15:beef", "rdap_host.json")); + "ns2.cat.lol", + null, + "4-ROID", + "v6", + "bad:f00d:cafe::15:beef", + "rdap_host.json")); assertThat(response.getStatus()).isEqualTo(200); } } diff --git a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json index 6fc583421..a1bc23748 100644 --- a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json +++ b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts.json @@ -14,15 +14,13 @@ "type" : "application/rdap+json" } ], - "events": [ + "remarks": [ { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "2000-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" + "title": "Incomplete Data", + "description": [ + "Summary data only. For complete data, send a specific query for the object." + ], + "type": "object truncated due to unexplainable reasons" } ], "vcardArray" : @@ -63,15 +61,13 @@ "type" : "application/rdap+json" } ], - "events": [ + "remarks": [ { - "eventAction": "registration", - "eventActor": "2-Registrar", - "eventDate": "2000-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" + "title": "Incomplete Data", + "description": [ + "Summary data only. For complete data, send a specific query for the object." + ], + "type": "object truncated due to unexplainable reasons" } ], "publicIds" : @@ -102,41 +98,6 @@ ["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551213"], ["email", {}, "text", "contact-us@example.com"] ] - ], - "entities" : - [ - { - "objectClassName" : "entity", - "status" : ["active"], - "roles" : ["administrative"], - "vcardArray" : - [ - "vcard", - [ - ["version", {}, "text", "4.0"], - ["fn", {}, "text", "Jane Doe"], - ["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551215"], - ["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551216"], - ["email", {}, "text", "janedoe@example.com"] - ] - ], - }, - { - "objectClassName" : "entity", - "status" : ["active"], - "roles" : ["technical"], - "vcardArray" : - [ - "vcard", - [ - ["version", {}, "text", "4.0"], - ["fn", {}, "text", "John Doe"], - ["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551213"], - ["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551213"], - ["email", {}, "text", "johndoe@example.com"] - ] - ], - } ] } ], diff --git a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json index 799076ec3..f99fa92a6 100644 --- a/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json +++ b/javatests/google/registry/rdap/testdata/rdap_multiple_contacts2.json @@ -14,15 +14,13 @@ "type" : "application/rdap+json" } ], - "events": [ + "remarks": [ { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "2000-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" + "title": "Incomplete Data", + "description": [ + "Summary data only. For complete data, send a specific query for the object." + ], + "type": "object truncated due to unexplainable reasons" } ], "vcardArray" : @@ -62,15 +60,13 @@ "type" : "application/rdap+json" } ], - "events": [ + "remarks": [ { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "2000-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" + "title": "Incomplete Data", + "description": [ + "Summary data only. For complete data, send a specific query for the object." + ], + "type": "object truncated due to unexplainable reasons" } ], "vcardArray" : diff --git a/javatests/google/registry/rdap/testdata/rdap_multiple_domains.json b/javatests/google/registry/rdap/testdata/rdap_multiple_domains.json index 815a27ea1..4c1375fd3 100644 --- a/javatests/google/registry/rdap/testdata/rdap_multiple_domains.json +++ b/javatests/google/registry/rdap/testdata/rdap_multiple_domains.json @@ -16,371 +16,17 @@ "value": "https://example.com/rdap/domain/cat.example" } ], - "events": [ - { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "2000-01-01T00:00:00.000Z" - }, - { - "eventAction": "expiration", - "eventDate": "2110-10-08T00:44:59.000Z" - }, - { - "eventAction": "last changed", - "eventDate": "2009-05-29T20:13:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" - } - ], - "nameservers": [ - { - "status": [ - "active" - ], - "handle": "8-ROID", - "links": [ - { - "href": "https://example.com/rdap/nameserver/ns1.cat.lol", - "type": "application/rdap+json", - "rel": "self", - "value": "https://example.com/rdap/nameserver/ns1.cat.lol" - } - ], - "ldhName": "ns1.cat.lol", - "ipAddresses": { - "v4": [ - "1.2.3.4" - ] - }, - "events": [ - { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "1999-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" - } - ], - "objectClassName": "nameserver" - }, - { - "status": [ - "active" - ], - "handle": "1F-ROID", - "links": [ - { - "href": "https://example.com/rdap/nameserver/ns2.external.tld", - "type": "application/rdap+json", - "rel": "self", - "value": "https://example.com/rdap/nameserver/ns2.external.tld" - } - ], - "ldhName": "ns2.external.tld", - "ipAddresses": { - "v6": [ - "bad:f00d:cafe::15:beef" - ] - }, - "events": [ - { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "1998-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" - } - ], - "objectClassName": "nameserver" - } - ], "ldhName": "cat.example", - "entities": [ + "objectClassName": "domain", + "remarks": [ { - "status": [ - "active" + "title": "Incomplete Data", + "description": [ + "Summary data only. For complete data, send a specific query for the object." ], - "handle": "1B-ROID", - "roles": [ - "administrative" - ], - "links": [ - { - "href": "https://example.com/rdap/entity/1B-ROID", - "type": "application/rdap+json", - "rel": "self", - "value": "https://example.com/rdap/entity/1B-ROID" - } - ], - "events": [ - { - "eventAction": "registration", - "eventActor": "foo", - "eventDate": "1998-01-01T00:00:00.000Z" - }, - { - "eventAction": "last update of RDAP database", - "eventDate": "2000-01-01T00:00:00.000Z" - } - ], - "objectClassName": "entity", - "vcardArray": [ - "vcard", - [ - [ - "version", - {}, - "text", - "4.0" - ], - [ - "fn", - {}, - "text", - "Mark" - ], - [ - "org", - {}, - "text", - "GOOGLE INCORPORATED