diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 5d31c485c..5dd547e26 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -1135,6 +1135,12 @@ public final class RegistryConfig { return config.registryPolicy.allocationTokenCustomLogicClass; } + @Provides + @Config("dnsCountQueryCoordinatorClass") + public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) { + return config.registryPolicy.dnsCountQueryCoordinatorClass; + } + /** Returns the disclaimer text for the exported premium terms. */ @Provides @Config("premiumTermsExportDisclaimer") diff --git a/java/google/registry/config/RegistryConfigSettings.java b/java/google/registry/config/RegistryConfigSettings.java index a60301e59..e7b1eac16 100644 --- a/java/google/registry/config/RegistryConfigSettings.java +++ b/java/google/registry/config/RegistryConfigSettings.java @@ -77,6 +77,7 @@ public class RegistryConfigSettings { public String customLogicFactoryClass; public String whoisCommandFactoryClass; public String allocationTokenCustomLogicClass; + public String dnsCountQueryCoordinatorClass; public int contactAutomaticTransferDays; public String greetingServerId; public List registrarChangesNotificationEmailAddresses; diff --git a/java/google/registry/config/files/default-config.yaml b/java/google/registry/config/files/default-config.yaml index e5a9de33e..17e2bbf19 100644 --- a/java/google/registry/config/files/default-config.yaml +++ b/java/google/registry/config/files/default-config.yaml @@ -55,6 +55,10 @@ registryPolicy: # See flows/domain/token/AllocationTokenCustomLogic.java allocationTokenCustomLogicClass: google.registry.flows.domain.token.AllocationTokenCustomLogic + # Custom logic class for handling DNS query count reporting for ICANN. + # See reporting/icann/DnsCountQueryCoordinator.java + dnsCountQueryCoordinatorClass: google.registry.reporting.icann.BasicDnsCountQueryCoordinator + # Length of time after which contact transfers automatically conclude. contactAutomaticTransferDays: 5 diff --git a/java/google/registry/module/backend/BackendRequestComponent.java b/java/google/registry/module/backend/BackendRequestComponent.java index fad4b57f1..f1236d4f4 100644 --- a/java/google/registry/module/backend/BackendRequestComponent.java +++ b/java/google/registry/module/backend/BackendRequestComponent.java @@ -73,6 +73,7 @@ import google.registry.reporting.billing.BillingModule; import google.registry.reporting.billing.CopyDetailReportsAction; import google.registry.reporting.billing.GenerateInvoicesAction; import google.registry.reporting.billing.PublishInvoicesAction; +import google.registry.reporting.icann.DnsCountQueryCoordinatorModule; import google.registry.reporting.icann.IcannReportingModule; import google.registry.reporting.icann.IcannReportingStagingAction; import google.registry.reporting.icann.IcannReportingUploadAction; @@ -100,6 +101,7 @@ import google.registry.tmch.TmchSmdrlAction; BillingModule.class, CloudDnsWriterModule.class, CronModule.class, + DnsCountQueryCoordinatorModule.class, DnsModule.class, DnsUpdateConfigModule.class, DnsUpdateWriterModule.class, diff --git a/java/google/registry/reporting/icann/ActivityReportingQueryBuilder.java b/java/google/registry/reporting/icann/ActivityReportingQueryBuilder.java index 18a1aeaf2..9aeafe905 100644 --- a/java/google/registry/reporting/icann/ActivityReportingQueryBuilder.java +++ b/java/google/registry/reporting/icann/ActivityReportingQueryBuilder.java @@ -29,9 +29,7 @@ import org.joda.time.YearMonth; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; -/** - * Utility class that produces SQL queries used to generate activity reports from Bigquery. - */ +/** Utility class that produces SQL queries used to generate activity reports from Bigquery. */ public final class ActivityReportingQueryBuilder implements QueryBuilder { // Names for intermediary tables for overall activity reporting query. @@ -42,20 +40,23 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder { static final String WHOIS_COUNTS = "whois_counts"; static final String ACTIVITY_REPORT_AGGREGATION = "activity_report_aggregation"; - @Inject @Config("projectId") String projectId; + @Inject + @Config("projectId") + String projectId; @Inject YearMonth yearMonth; - @Inject ActivityReportingQueryBuilder() {} + @Inject DnsCountQueryCoordinator dnsCountQueryCoordinator; + + @Inject + ActivityReportingQueryBuilder() {} /** Returns the aggregate query which generates the activity report from the saved view. */ @Override public String getReportQuery() { return String.format( "#standardSQL\nSELECT * FROM `%s.%s.%s`", - projectId, - ICANN_REPORTING_DATA_SET, - getTableName(ACTIVITY_REPORT_AGGREGATION)); + projectId, ICANN_REPORTING_DATA_SET, getTableName(ACTIVITY_REPORT_AGGREGATION)); } /** Sets the month we're doing activity reporting for, and returns the view query map. */ @@ -67,6 +68,10 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder { return createQueryMap(firstDayOfMonth, lastDayOfMonth); } + public void prepareForQuery() throws Exception { + dnsCountQueryCoordinator.prepareForQuery(); + } + /** Returns a map from view name to its associated SQL query. */ private ImmutableMap createQueryMap( LocalDate firstDayOfMonth, LocalDate lastDayOfMonth) { @@ -80,8 +85,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder { .build(); queriesBuilder.put(getTableName(REGISTRAR_OPERATING_STATUS), operationalRegistrarsQuery); - String dnsCountsQuery = - SqlTemplate.create(getQueryFromFile("dns_counts.sql")).build(); + String dnsCountsQuery = dnsCountQueryCoordinator.createQuery(); queriesBuilder.put(getTableName(DNS_COUNTS), dnsCountsQuery); // Convert reportingMonth into YYYYMMDD format for Bigquery table partition pattern-matching. @@ -133,7 +137,6 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder { return queriesBuilder.build(); } - /** Returns the table name of the query, suffixed with the yearMonth in _yyyyMM format. */ private String getTableName(String queryName) { return String.format("%s_%s", queryName, DateTimeFormat.forPattern("yyyyMM").print(yearMonth)); diff --git a/java/google/registry/reporting/icann/BasicDnsCountQueryCoordinator.java b/java/google/registry/reporting/icann/BasicDnsCountQueryCoordinator.java new file mode 100644 index 000000000..b35150b3f --- /dev/null +++ b/java/google/registry/reporting/icann/BasicDnsCountQueryCoordinator.java @@ -0,0 +1,38 @@ +// Copyright 2018 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.reporting.icann; + +import com.google.common.io.Resources; +import google.registry.util.ResourceUtils; +import google.registry.util.SqlTemplate; + +/** + * DNS Count query for the basic case. + */ +public class BasicDnsCountQueryCoordinator implements DnsCountQueryCoordinator { + + BasicDnsCountQueryCoordinator(DnsCountQueryCoordinator.Params params) {} + + @Override + public String createQuery() { + return SqlTemplate.create( + ResourceUtils.readResourceUtf8( + Resources.getResource(this.getClass(), "sql/" + "dns_counts.sql"))) + .build(); + } + + @Override + public void prepareForQuery() throws Exception {} +} diff --git a/java/google/registry/reporting/icann/DnsCountQueryCoordinator.java b/java/google/registry/reporting/icann/DnsCountQueryCoordinator.java new file mode 100644 index 000000000..1f2f9c10a --- /dev/null +++ b/java/google/registry/reporting/icann/DnsCountQueryCoordinator.java @@ -0,0 +1,58 @@ +// Copyright 2018 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.reporting.icann; + +import google.registry.bigquery.BigqueryConnection; +import org.joda.time.YearMonth; + +/** + * Methods for preparing and querying DNS statistics. + * + *

DNS systems may have different ways of providing this information, so it's useful to + * modularize this. + * + *

Derived classes must provide a constructor that accepts a + * {@link google.registry.reporting.icann.DnsCountQueryCoordinator.Params}. To override this, + * define dnsCountQueryCoordinatorClass in your config file. + */ +public interface DnsCountQueryCoordinator { + + /** + * Class to carry parameters for a new coordinator. + * + * If your report query requires any additional parameters, add them here. + */ + public class Params { + public BigqueryConnection bigquery; + + /** The year and month of the report. */ + public YearMonth yearMonth; + + /** The Google Cloud project id. */ + public String projectId; + + public Params(BigqueryConnection bigquery, YearMonth yearMonth, String projectId) { + this.bigquery = bigquery; + this.yearMonth = yearMonth; + this.projectId = projectId; + } + } + + /** Creates the string used to query bigtable for DNS count information. */ + String createQuery(); + + /** Do any necessry preparation for the DNS query. */ + void prepareForQuery() throws Exception; +} diff --git a/java/google/registry/reporting/icann/DnsCountQueryCoordinatorModule.java b/java/google/registry/reporting/icann/DnsCountQueryCoordinatorModule.java new file mode 100644 index 000000000..bbf7241ff --- /dev/null +++ b/java/google/registry/reporting/icann/DnsCountQueryCoordinatorModule.java @@ -0,0 +1,42 @@ +// Copyright 2018 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.reporting.icann; + +import static google.registry.util.TypeUtils.getClassFromString; +import static google.registry.util.TypeUtils.instantiate; + +import dagger.Module; +import dagger.Provides; +import google.registry.bigquery.BigqueryConnection; +import google.registry.config.RegistryConfig.Config; +import org.joda.time.YearMonth; + +/** Dagger module to provide the DnsCountQueryCoordinator. */ +@Module +public class DnsCountQueryCoordinatorModule { + + @Provides + static DnsCountQueryCoordinator provideDnsCountQueryCoordinator( + @Config("dnsCountQueryCoordinatorClass") String customClass, + BigqueryConnection bigquery, + YearMonth yearMonth, + @Config("projectId") String projectId) { + DnsCountQueryCoordinator.Params params = + new DnsCountQueryCoordinator.Params(bigquery, yearMonth, projectId); + DnsCountQueryCoordinator result = + instantiate(getClassFromString(customClass, DnsCountQueryCoordinator.class), params); + return result; + } +} diff --git a/java/google/registry/reporting/icann/IcannReportingStager.java b/java/google/registry/reporting/icann/IcannReportingStager.java index a988c29c6..1f998ad61 100644 --- a/java/google/registry/reporting/icann/IcannReportingStager.java +++ b/java/google/registry/reporting/icann/IcannReportingStager.java @@ -83,6 +83,10 @@ public class IcannReportingStager { QueryBuilder queryBuilder = (reportType == ReportType.ACTIVITY) ? activityQueryBuilder : transactionsQueryBuilder; + if (reportType == ReportType.ACTIVITY) { + // Prepare for the DNS count query, which may have special needs. + activityQueryBuilder.prepareForQuery(); + } ImmutableMap viewQueryMap = queryBuilder.getViewQueryMap(); // Generate intermediary views diff --git a/java/google/registry/util/TypeUtils.java b/java/google/registry/util/TypeUtils.java index 614b66314..4d5658571 100644 --- a/java/google/registry/util/TypeUtils.java +++ b/java/google/registry/util/TypeUtils.java @@ -56,6 +56,22 @@ public class TypeUtils { } } + /** + * Instantiate a class with the specified constructor argument. + * + *

Because we use arg1's type to lookup the constructor, this only works if arg1's class is + * exactly the same type as the constructor argument. Subtypes are not allowed. + */ + public static T instantiate(Class clazz, U arg1) { + checkArgument(Modifier.isPublic(clazz.getModifiers()), + "AppEngine's custom security manager won't let us reflectively access non-public types"); + try { + return clazz.getConstructor(arg1.getClass()).newInstance(arg1); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + /** * Returns the class referred to by a fully qualified class name string. * diff --git a/javatests/google/registry/reporting/icann/ActivityReportingQueryBuilderTest.java b/javatests/google/registry/reporting/icann/ActivityReportingQueryBuilderTest.java index a4f338f69..21e4ac9ce 100644 --- a/javatests/google/registry/reporting/icann/ActivityReportingQueryBuilderTest.java +++ b/javatests/google/registry/reporting/icann/ActivityReportingQueryBuilderTest.java @@ -31,6 +31,10 @@ public class ActivityReportingQueryBuilderTest { ActivityReportingQueryBuilder queryBuilder = new ActivityReportingQueryBuilder(); queryBuilder.yearMonth = new YearMonth(2017, 9); queryBuilder.projectId = "domain-registry-alpha"; + queryBuilder.dnsCountQueryCoordinator = + new BasicDnsCountQueryCoordinator( + new BasicDnsCountQueryCoordinator.Params(null, queryBuilder.yearMonth, + queryBuilder.projectId)); return queryBuilder; } @@ -63,5 +67,4 @@ public class ActivityReportingQueryBuilderTest { .isEqualTo(ReportingTestData.loadFile(testFilename)); } } - } diff --git a/javatests/google/registry/reporting/icann/IcannReportingStagerTest.java b/javatests/google/registry/reporting/icann/IcannReportingStagerTest.java index b7218382c..57fd56590 100644 --- a/javatests/google/registry/reporting/icann/IcannReportingStagerTest.java +++ b/javatests/google/registry/reporting/icann/IcannReportingStagerTest.java @@ -62,6 +62,7 @@ public class IcannReportingStagerTest { ActivityReportingQueryBuilder activityBuilder = new ActivityReportingQueryBuilder(); activityBuilder.projectId = "test-project"; activityBuilder.yearMonth = new YearMonth(2017, 6); + activityBuilder.dnsCountQueryCoordinator = new BasicDnsCountQueryCoordinator(null); action.activityQueryBuilder = activityBuilder; TransactionsReportingQueryBuilder transactionsBuilder = new TransactionsReportingQueryBuilder(); transactionsBuilder.projectId = "test-project"; diff --git a/javatests/google/registry/util/TypeUtilsTest.java b/javatests/google/registry/util/TypeUtilsTest.java index 6d2b310af..c0317be14 100644 --- a/javatests/google/registry/util/TypeUtilsTest.java +++ b/javatests/google/registry/util/TypeUtilsTest.java @@ -52,4 +52,22 @@ public class TypeUtilsTest { .hasMessageThat() .contains("Failed to load class com.fake.company.nonexistent.Class"); } + + public static class ExampleClass { + String val; + + public ExampleClass(String val) { + this.val = val; + } + } + + @Test + public void test_instantiateWithArg() { + Class clazz = + TypeUtils.getClassFromString( + "google.registry.util.TypeUtilsTest$ExampleClass", ExampleClass.class); + + ExampleClass result = TypeUtils.instantiate(clazz, "test"); + assertThat(result.val).isEqualTo("test"); + } }