google-nomulus/java/google/registry/billing/CopyDetailReportsAction.java
larryruili a42f18798e Move invoice generation to billing bucket and improve emailing
This moves the new pipeline's invoice generation to the billing bucket, under the 'invoices/yyyy-MM' subdirectory.

This also changes the invoice e-mail to use a multipart message that attaches the invoice to the e-mail, to guarantee the correct MIME type and download.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=181746191
2018-01-19 14:34:14 -05:00

140 lines
5.6 KiB
Java

// 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.billing;
import static google.registry.request.Action.Method.POST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.net.MediaType;
import google.registry.billing.BillingModule.InvoiceDirectoryPrefix;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.storage.drive.DriveConnection;
import google.registry.util.FormattingLogger;
import google.registry.util.Retrier;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import javax.inject.Inject;
/** Copy all registrar detail reports in a given bucket's subdirectory from GCS to Drive. */
@Action(path = CopyDetailReportsAction.PATH, method = POST, auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public final class CopyDetailReportsAction implements Runnable {
public static final String PATH = "/_dr/task/copyDetailReports";
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private final String billingBucket;
private final String invoiceDirectoryPrefix;
private final DriveConnection driveConnection;
private final GcsUtils gcsUtils;
private final Retrier retrier;
private final Response response;
private final BillingEmailUtils emailUtils;
@Inject
CopyDetailReportsAction(
@Config("billingBucket") String billingBucket,
@InvoiceDirectoryPrefix String invoiceDirectoryPrefix,
DriveConnection driveConnection,
GcsUtils gcsUtils,
Retrier retrier,
Response response,
BillingEmailUtils emailUtils) {
this.billingBucket = billingBucket;
this.invoiceDirectoryPrefix = invoiceDirectoryPrefix;
this.driveConnection = driveConnection;
this.gcsUtils = gcsUtils;
this.retrier = retrier;
this.response = response;
this.emailUtils = emailUtils;
}
@Override
public void run() {
ImmutableList<String> detailReportObjectNames;
try {
detailReportObjectNames =
gcsUtils
.listFolderObjects(billingBucket, invoiceDirectoryPrefix)
.stream()
.filter(objectName -> objectName.startsWith(BillingModule.DETAIL_REPORT_PREFIX))
.collect(ImmutableList.toImmutableList());
} catch (IOException e) {
logger.severefmt("Copy failed due to %s", e.getMessage());
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
response.setPayload(String.format("Failure, encountered %s", e.getMessage()));
return;
}
for (String detailReportName : detailReportObjectNames) {
// The standard report format is "invoice_details_yyyy-MM_registrarId_tld.csv
// TODO(larryruili): Determine a safer way of enforcing this.
String registrarId = detailReportName.split("_")[3];
Optional<Registrar> registrar = Registrar.loadByClientId(registrarId);
if (!registrar.isPresent()) {
logger.warningfmt(
"Registrar %s not found in database for file %s", registrar, detailReportName);
continue;
}
String driveFolderId = registrar.get().getDriveFolderId();
if (driveFolderId == null) {
logger.warningfmt("Drive folder id not found for registrar %s", registrarId);
continue;
}
// Attempt to copy each detail report to its associated registrar's drive folder.
retrier.callWithRetry(
() -> {
try (InputStream input =
gcsUtils.openInputStream(
new GcsFilename(billingBucket, invoiceDirectoryPrefix + detailReportName))) {
driveConnection.createFile(
detailReportName,
MediaType.CSV_UTF_8,
driveFolderId,
ByteStreams.toByteArray(input));
logger.infofmt(
"Published detail report for %s to folder %s using GCS file gs://%s/%s.",
registrarId, driveFolderId, billingBucket, detailReportName);
}
},
new Retrier.FailureReporter() {
@Override
public void beforeRetry(Throwable thrown, int failures, int maxAttempts) {}
@Override
public void afterFinalFailure(Throwable thrown, int failures) {
emailUtils.sendAlertEmail(
String.format(
"Warning: CopyDetailReportsAction failed.\nEncountered: %s on file: %s",
thrown.getMessage(), detailReportName));
}
},
IOException.class);
}
response.setStatus(SC_OK);
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
response.setPayload("Copied detail reports.");
}
}