From b5e421cee312ea08b86e6078b3342f34eb20518d Mon Sep 17 00:00:00 2001 From: mountford Date: Thu, 22 Sep 2016 11:00:37 -0700 Subject: [PATCH] RDAP: Display summary data for search result items It appears to be standard RDAP practice when returning result sets for domain, nameserver and entity searches to give only summary data for each result item. Any information that can be gleaned from the object itself is included, but related resources are not included. For a domain, for instance, the domain information is included, but nameservers, entities and events (which come from history entries) are suppressed. In their place, there is a standard boilerplate remark in the object indicating that only summary data is included, and that the user should query the item directly to get the full data. Note that summary data is used only for searches; direct queries for an item will still return full data. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=133973835 --- java/google/registry/rdap/RdapActionBase.java | 2 +- .../registry/rdap/RdapAutnumAction.java | 3 +- .../registry/rdap/RdapDomainAction.java | 6 +- .../registry/rdap/RdapDomainSearchAction.java | 87 ++- .../registry/rdap/RdapEntityAction.java | 9 +- .../registry/rdap/RdapEntitySearchAction.java | 87 ++- java/google/registry/rdap/RdapHelpAction.java | 4 +- .../rdap/RdapIcannStandardInformation.java | 11 + java/google/registry/rdap/RdapIpAction.java | 3 +- .../registry/rdap/RdapJsonFormatter.java | 372 +++++---- .../registry/rdap/RdapNameserverAction.java | 6 +- .../rdap/RdapNameserverSearchAction.java | 87 ++- .../registry/rdap/RdapActionBaseTest.java | 9 +- .../rdap/RdapDomainSearchActionTest.java | 33 +- .../registry/rdap/RdapJsonFormatterTest.java | 101 ++- .../rdap/RdapNameserverSearchActionTest.java | 7 +- .../rdap/testdata/rdap_multiple_contacts.json | 63 +- .../testdata/rdap_multiple_contacts2.json | 28 +- .../rdap/testdata/rdap_multiple_domains.json | 736 +----------------- .../rdap/testdata/rdap_multiple_hosts.json | 28 +- .../testdata/rdapjson_domain_summary.json | 31 + .../testdata/rdapjson_host_both_summary.json | 30 + .../testdata/rdapjson_registrant_summary.json | 36 + .../testdata/rdapjson_registrar_summary.json | 50 ++ .../registry/testing/DatastoreHelper.java | 64 +- 25 files changed, 773 insertions(+), 1120 deletions(-) create mode 100644 javatests/google/registry/rdap/testdata/rdapjson_domain_summary.json create mode 100644 javatests/google/registry/rdap/testdata/rdapjson_host_both_summary.json create mode 100644 javatests/google/registry/rdap/testdata/rdapjson_registrant_summary.json create mode 100644 javatests/google/registry/rdap/testdata/rdapjson_registrar_summary.json 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