Java tutorial
// Copyright 2016 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package google.registry.rdap; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTICES; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; import static google.registry.util.DateTimeUtils.END_OF_TIME; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.primitives.Booleans; import google.registry.config.RegistryConfig.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.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; /** * RDAP (new WHOIS) action for nameserver search requests. * * <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485. * * @see <a href="http://tools.ietf.org/html/rfc7482"> * RFC 7482: Registration Data Access Protocol (RDAP) Query Format</a> * @see <a href="http://tools.ietf.org/html/rfc7483"> * RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a> */ @Action(path = RdapNameserverSearchAction.PATH, method = { GET, HEAD }, isPrefix = true) public class RdapNameserverSearchAction extends RdapActionBase { public static final String PATH = "/rdap/nameservers"; @Inject Clock clock; @Inject @Parameter("name") Optional<String> nameParam; @Inject @Parameter("ip") Optional<InetAddress> ipParam; @Inject @Config("rdapResultSetMaxSize") int rdapResultSetMaxSize; @Inject RdapNameserverSearchAction() { } @Override public String getHumanReadableObjectTypeName() { return "nameserver search"; } @Override public String getActionPath() { return PATH; } /** Parses the parameters and calls the appropriate search function. */ @Override public ImmutableMap<String, Object> getJsonObjectForResource(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. if (pathSearchString.length() > 0) { throw new BadRequestException("Unexpected path"); } if (Booleans.countTrue(nameParam.isPresent(), ipParam.isPresent()) != 1) { throw new BadRequestException("You must specify either name=XXXX or ip=YYYY"); } RdapSearchResults results; if (nameParam.isPresent()) { // syntax: /rdap/nameservers?name=exam*.com if (!LDH_PATTERN.matcher(nameParam.get()).matches()) { throw new BadRequestException("Name parameter must contain only letters, dots" + " and hyphens, and an optional single wildcard"); } results = searchByName(RdapSearchPattern.create(Idn.toASCII(nameParam.get()), true), now); } else { // syntax: /rdap/nameservers?ip=1.2.3.4 results = searchByIp(ipParam.get(), now); } if (results.jsonList().isEmpty()) { throw new NotFoundException("No nameservers found"); } ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>(); jsonBuilder.put("nameserverSearchResults", results.jsonList()); rdapJsonFormatter.addTopLevelEntries(jsonBuilder, BoilerplateType.NAMESERVER, results.isTruncated() ? TRUNCATION_NOTICES : ImmutableList.<ImmutableMap<String, Object>>of(), ImmutableList.<ImmutableMap<String, Object>>of(), rdapLinkBase); return jsonBuilder.build(); } /** Searches for nameservers by name, returning a JSON array of nameserver info maps. */ private RdapSearchResults searchByName(final RdapSearchPattern partialStringQuery, final DateTime now) { // Handle queries without a wildcard -- just load by foreign key. if (!partialStringQuery.getHasWildcard()) { HostResource hostResource = loadByForeignKey(HostResource.class, partialStringQuery.getInitialString(), now); if (hostResource == null) { throw new NotFoundException("No nameservers found"); } return RdapSearchResults.create(ImmutableList.of(rdapJsonFormatter.makeRdapJsonForHost(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) { return makeSearchResults( // Add 1 so we can detect truncation. queryUndeleted(HostResource.class, "fullyQualifiedHostName", partialStringQuery, rdapResultSetMaxSize + 1).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. } else { DomainResource domainResource = loadByForeignKey(DomainResource.class, partialStringQuery.getSuffix(), now); if (domainResource == null) { throw new NotFoundException("No domain found for specified nameserver suffix"); } ImmutableList.Builder<HostResource> 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 = loadByForeignKey(HostResource.class, fqhn, now); if (hostResource != null) { hostListBuilder.add(hostResource); } } } return makeSearchResults(hostListBuilder.build(), now); } } /** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */ private RdapSearchResults searchByIp(final InetAddress inetAddress, DateTime now) { return makeSearchResults( // Add 1 so we can detect truncation. ofy().load().type(HostResource.class).filter("inetAddresses", inetAddress.getHostAddress()) .filter("deletionTime", END_OF_TIME).limit(rdapResultSetMaxSize + 1).list(), now); } /** Output JSON for a list of hosts. */ private RdapSearchResults makeSearchResults(List<HostResource> hosts, DateTime now) { OutputDataType outputDataType = (hosts.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; ImmutableList.Builder<ImmutableMap<String, Object>> jsonListBuilder = new ImmutableList.Builder<>(); for (HostResource host : Iterables.limit(hosts, rdapResultSetMaxSize)) { jsonListBuilder.add(rdapJsonFormatter.makeRdapJsonForHost(host, false, rdapLinkBase, rdapWhoisServer, now, outputDataType)); } ImmutableList<ImmutableMap<String, Object>> jsonList = jsonListBuilder.build(); return RdapSearchResults.create(jsonList, jsonList.size() < hosts.size()); } }