// Copyright 2017 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; import static google.registry.request.RequestParameters.extractOptionalEnumParameter; import static google.registry.request.RequestParameters.extractOptionalParameter; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.MoreExecutors; import dagger.Module; import dagger.Provides; import google.registry.bigquery.BigqueryConnection; import google.registry.request.HttpException.BadRequestException; import google.registry.request.Parameter; import google.registry.util.Clock; import google.registry.util.SendEmailService; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import org.joda.time.Duration; import org.joda.time.format.DateTimeFormat; /** Module for dependencies required by ICANN monthly transactions/activity reporting. */ @Module public final class IcannReportingModule { /** Enum determining the type of report to generate or upload. */ public enum ReportType { TRANSACTIONS, ACTIVITY } static final String PARAM_OPTIONAL_YEAR_MONTH = "yearMonthOptional"; static final String PARAM_YEAR_MONTH = "yearMonth"; static final String PARAM_OPTIONAL_SUBDIR = "subdirOptional"; static final String PARAM_SUBDIR = "subdir"; static final String PARAM_REPORT_TYPE = "reportType"; static final String ICANN_REPORTING_DATA_SET = "icann_reporting"; static final String DATASTORE_EXPORT_DATA_SET = "latest_datastore_export"; static final String MANIFEST_FILE_NAME = "MANIFEST.txt"; private static final String DEFAULT_SUBDIR = "icann/monthly"; private static final String BIGQUERY_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; /** Extracts an optional yearMonth in yyyy-MM format from the request. */ @Provides @Parameter(PARAM_OPTIONAL_YEAR_MONTH) static Optional provideYearMonthOptional(HttpServletRequest req) { return extractOptionalParameter(req, PARAM_YEAR_MONTH); } /** Provides the yearMonth in yyyy-MM format, defaults to one month prior to run time. */ @Provides @Parameter(PARAM_YEAR_MONTH) static String provideYearMonth( @Parameter(PARAM_OPTIONAL_YEAR_MONTH) Optional yearMonthOptional, Clock clock) { String yearMonth = yearMonthOptional.orElse( DateTimeFormat.forPattern("yyyy-MM").print(clock.nowUtc().minusMonths(1))); if (!yearMonth.matches("[0-9]{4}-[0-9]{2}")) { throw new BadRequestException( String.format("yearMonth must be in yyyy-MM format, got %s instead", yearMonth)); } return yearMonth; } /** Provides an optional subdirectory to store/upload reports to, extracted from the request. */ @Provides @Parameter(PARAM_OPTIONAL_SUBDIR) static Optional provideSubdirOptional(HttpServletRequest req) { return extractOptionalParameter(req, PARAM_SUBDIR); } /** Provides the subdirectory to store/upload reports to, defaults to icann/monthly/yearMonth. */ @Provides @Parameter(PARAM_SUBDIR) static String provideSubdir( @Parameter(PARAM_OPTIONAL_SUBDIR) Optional subdirOptional, @Parameter(PARAM_YEAR_MONTH) String yearMonth) { String subdir = subdirOptional.orElse(String.format("%s/%s", DEFAULT_SUBDIR, yearMonth)); if (subdir.startsWith("/") || subdir.endsWith("/")) { throw new BadRequestException( String.format("subdir must not start or end with a \"/\", got %s instead.", subdir)); } return subdir; } /** Provides an optional reportType to store/upload reports to, extracted from the request. */ @Provides static Optional provideReportTypeOptional(HttpServletRequest req) { return extractOptionalEnumParameter(req, ReportType.class, PARAM_REPORT_TYPE); } /** Provides a list of reportTypes specified. If absent, we default to both report types. */ @Provides @Parameter(PARAM_REPORT_TYPE) static ImmutableList provideReportTypes(Optional reportTypeOptional) { return reportTypeOptional.map(ImmutableList::of) .orElseGet(() -> ImmutableList.of(ReportType.ACTIVITY, ReportType.TRANSACTIONS)); } /** * Constructs a BigqueryConnection with default settings. * *

We use Bigquery to generate ICANN monthly reports via large aggregate SQL queries. * * @see ActivityReportingQueryBuilder * @see google.registry.tools.BigqueryParameters for justifications of defaults. */ @Provides static BigqueryConnection provideBigqueryConnection(HttpTransport transport) { try { GoogleCredential credential = GoogleCredential .getApplicationDefault(transport, new JacksonFactory()); BigqueryConnection connection = new BigqueryConnection.Builder() .setExecutorService(MoreExecutors.newDirectExecutorService()) .setCredential(credential.createScoped(ImmutableList.of(BIGQUERY_SCOPE))) .setDatasetId(ICANN_REPORTING_DATA_SET) .setOverwrite(true) .setPollInterval(Duration.standardSeconds(1)) .build(); connection.initialize(); return connection; } catch (Throwable e) { throw new RuntimeException("Could not initialize BigqueryConnection!", e); } } @Provides static SendEmailService provideSendEmailService() { return new SendEmailService(); } }