// Copyright 2015 Google Inc. 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 com.google.api.ads.dfp.jaxws.utils.v201702;
import static org.apache.commons.lang.CharEncoding.UTF_8;
import com.google.api.ads.dfp.jaxws.v201702.ApiException_Exception;
import com.google.api.ads.dfp.jaxws.v201702.ExportFormat;
import com.google.api.ads.dfp.jaxws.v201702.ReportDownloadOptions;
import com.google.api.ads.dfp.jaxws.v201702.ReportJobStatus;
import com.google.api.ads.dfp.jaxws.v201702.ReportServiceInterface;
import com.google.api.ads.dfp.lib.utils.ReportCallback;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteSource;
import com.google.common.io.CharSource;
import com.google.common.io.Resources;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* Retrieves reports using a {@link ReportServiceInterface}.
* <p>
* There are two main functions of this class:
* <ul>
* <li>To download a report in Gzip format to a file or any {@code OutputStream}
* </li>
* <li>To get the report and perform a number of tasks on it</li>
* </ul>
* <p>
* {@code ReportUtils} also provides the method
* {@link #whenReportReady(ReportCallback)} to wait for a scheduled report to
* finish processing before taking an action on the report through the supplied
* {@link ReportCallback}.
*/
public class ReportDownloader {
public static final Charset REPORT_CHARSET = Charset.forName(UTF_8);
/** The time to sleep before each request to the service. */
public static final int SLEEP_TIMER = 30000;
private final ReportServiceInterface reportService;
private final long reportJobId;
private static final Set<ExportFormat> SUPPORTED_CHARSOUCE_EXPORT_FORMATS =
ImmutableSet.of(ExportFormat.CSV_DUMP, ExportFormat.TSV, ExportFormat.XML);
private static class GZippedByteSource extends ByteSource {
private ByteSource containedByteSource;
public GZippedByteSource(ByteSource zippedByteSource) {
containedByteSource = zippedByteSource;
}
@Override
public InputStream openStream() throws IOException {
return new GZIPInputStream(containedByteSource.openStream());
}
}
/**
* Constructs a {@code ReportDownloader} object for a
* {@link ReportServiceInterface} and a report job id that the the class works
* on.
*
* @param reportService the ReportService stub to make calls to
* @param reportJobId the report job ID
*/
public ReportDownloader(ReportServiceInterface reportService, long reportJobId) {
this.reportJobId = reportJobId;
this.reportService = reportService;
}
/**
* Waits for the report to be ready and then calls:
* <ul>
* <li>{@link ReportCallback#onSuccess()} for a successful scheduling</li>
* <li>{@link ReportCallback#onFailure()} for a failed scheduling due to a
* {@link ReportJobStatus#FAILED}</li>
* <li>{@link ReportCallback#onInterruption()} if the wait thread is
* interrupted</li>
* <li>{@link ReportCallback#onException(Exception)} if there was an exception
* while waiting for the report to finish</li>
* </ul>
*
* @param callback the {@code ReportCallback} to call when the job has
* finished, successfully or otherwise
* @throws IllegalArgumentException if {@code callback == null}
* @return the thread created that handles waiting for the report.
* {@link Thread#interrupt()} can be called on the returned thread to
* interrupt it.
*/
public Thread whenReportReady(final ReportCallback callback) {
Preconditions.checkNotNull(callback, "Report callback cannot be null.");
Thread waitThread = new Thread("ReportUtils.whenReportReady " + reportJobId) {
@Override
public void run() {
try {
if (waitForReportReady()) {
callback.onSuccess();
} else {
callback.onFailure();
}
} catch (ApiException_Exception e) {
callback.onException(e);
} catch (InterruptedException e) {
callback.onInterruption();
} catch (RuntimeException e) {
callback.onException(e);
}
}
};
waitThread.start();
return waitThread;
}
/**
* Blocks and waits for a report to be ready. When a {@link ReportJobStatus}
* is received that is not {@code ReportJobStatus#Pending} or {@code
* ReportJobStatus#InProgress}, the report is considered finished, and the
* method is returned with a {@code true} if the report was successful, or an
* {@code false} if not.
*
* @return {@code true} if the report was successful, {@code false} otherwise
* @throws ApiException_Exception if there was an error performing one of the SOAP
* calls
* @throws InterruptedException if the thread was interrupted
*/
public boolean waitForReportReady() throws InterruptedException, ApiException_Exception {
ReportJobStatus status = reportService.getReportJobStatus(reportJobId);
while (status == ReportJobStatus.IN_PROGRESS) {
Thread.sleep(SLEEP_TIMER);
status = reportService.getReportJobStatus(reportJobId);
}
return status == ReportJobStatus.COMPLETED;
}
/**
* Gets the download URL for a GZip or plain-text format report. If you requested
* a compressed report, you may want to save your file with a gz or zip extension.
*
* <pre><code>
* URL url = new URL(reportDownloader.getDownloadUrl(options));
* Resources.asByteSource(url).copyTo(Files.asByteSink(file));
* </code></pre>
*
* @param options the options to download the report with
* @return the URL for the report download
* @throws ApiException_Exception if there was an error performing any jaxws call
* @throws MalformedURLException if there is an error forming the download URL
* @throws IllegalStateException if the report is not ready to be downloaded
*/
public URL getDownloadUrl(ReportDownloadOptions options) throws ApiException_Exception,
MalformedURLException {
ReportJobStatus status = reportService.getReportJobStatus(reportJobId);
Preconditions.checkState(status == ReportJobStatus.COMPLETED, "Report " + reportJobId
+ " must be completed before downloading. It is currently: " + status);
return new URL(reportService.getReportDownloadUrlWithOptions(reportJobId, options));
}
/**
* Returns a CharSource of report contents with {@code ReportDownloadOptions}. The ExportFormat
* must be string-based, such as
* {@link com.google.api.ads.dfp.jaxws.v201702.ExportFormat#CSV_DUMP}.
*
* @param options the options to download the report with
* @return a new CharSource of report results
* @throws IOException if there was an error performing any I/O action, including any SOAP calls
* @throws ApiException_Exception if there was any problem making the SOAP
* call
* @throws IllegalStateException if the report is not ready to be downloaded
* @throws IllegalArgumentException if the {@link ExportFormat} is not a string-based format
*/
public CharSource getReportAsCharSource(ReportDownloadOptions options) throws IOException,
ApiException_Exception {
Preconditions.checkArgument(
SUPPORTED_CHARSOUCE_EXPORT_FORMATS.contains(options.getExportFormat()),
"ExportFormat " + options.getExportFormat() + " cannot be used with CharSource");
ByteSource byteSource = Resources.asByteSource(getDownloadUrl(options));
return (options.isUseGzipCompression() ? new GZippedByteSource(byteSource) : byteSource)
.asCharSource(REPORT_CHARSET);
}
}