File Coverage

blib/lib/Google/Ads/Common/ReportDownloadHandler.pm
Criterion Covered Total %
statement 66 155 42.5
branch 0 38 0.0
condition n/a
subroutine 22 29 75.8
pod 3 3 100.0
total 91 225 40.4


line stmt bran cond sub pod time code
1             # Copyright 2015, Google Inc. All Rights Reserved.
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14              
15             package Google::Ads::Common::ReportDownloadHandler;
16              
17 2     2   1086 use strict;
  2         4  
  2         61  
18 2     2   9 use warnings;
  2         4  
  2         51  
19 2     2   9 use utf8;
  2         4  
  2         13  
20 2     2   44 use version;
  2         4  
  2         14  
21              
22             # The following needs to be on one line because CPAN uses a particularly hacky
23             # eval() to determine module versions.
24 2     2   114 use Google::Ads::AdWords::Constants; our $VERSION = ${Google::Ads::AdWords::Constants::VERSION};
  2         4  
  2         103  
25              
26 2     2   13 use Google::Ads::AdWords::Logging;
  2         4  
  2         66  
27 2     2   11 use Google::Ads::AdWords::Reports::ReportingConfiguration;
  2         4  
  2         39  
28 2     2   260 use Google::Ads::AdWords::RequestStats;
  2         5  
  2         54  
29 2     2   12 use Google::Ads::Common::ReportDownloadError;
  2         2  
  2         40  
30 2     2   319 use Google::Ads::Common::Utilities::AdsUtilityRegistry;
  2         7  
  2         44  
31              
32 2     2   10 use Class::Std::Fast;
  2         5  
  2         12  
33              
34 2     2   502 use File::stat;
  2         5152  
  2         20  
35 2     2   447 use HTTP::Request;
  2         9276  
  2         63  
36 2     2   322 use HTTP::Status qw(:constants);
  2         3006  
  2         723  
37 2     2   15 use Log::Log4perl qw(:levels);
  2         4  
  2         22  
38 2     2   561 use LWP::UserAgent;
  2         11465  
  2         60  
39 2     2   383 use MIME::Base64;
  2         554  
  2         98  
40 2     2   299 use POSIX;
  2         5160  
  2         15  
41 2     2   6019 use Time::HiRes qw(gettimeofday tv_interval);
  2         4  
  2         23  
42 2     2   257 use URI::Escape;
  2         4  
  2         94  
43 2     2   690 use XML::Simple;
  2         8470  
  2         19  
44              
45 2     2   165 use constant SCRUBBED_HEADERS => qw(DeveloperToken Authorization);
  2         4  
  2         2585  
46              
47             my %client_of : ATTR(:name :default<>);
48             my %__user_agent_of : ATTR(:name<__user_agent> :default<>);
49             my %__http_request_of : ATTR(:name<__http_request> :default<>);
50             my %download_format_of : ATTR(:name :default<>);
51              
52             # Returns the report contents as a string. If the report fails then returns
53             # a ReportDownloadError.
54             sub get_as_string {
55 0     0 1   my ($self) = @_;
56              
57 0           Google::Ads::Common::Utilities::AdsUtilityRegistry->add_ads_utilities(
58             "ReportDownloaderString");
59              
60 0           my $user_agent = $self->get___user_agent();
61              
62 0           $self->__set_gzip_header();
63 0           my $start_time = [gettimeofday()];
64              
65 0           my $response = $user_agent->request($self->get___http_request());
66 0           $response = $self->__check_response($response, $start_time);
67 0 0         if (ref $response eq "Google::Ads::Common::ReportDownloadError") {
68 0           return $response;
69             }
70 0           return $response->decoded_content();
71             }
72              
73             # Saves the report response to a file. If the report fails then returns
74             # a ReportDownloadError. Otherwise, returns the HTTPResponse.
75             sub save {
76 0     0 1   my ($self, $file_path) = @_;
77 0 0         if (!$file_path) {
78 0           warn 'No file path provided';
79 0           return undef;
80             }
81              
82             Google::Ads::Common::Utilities::AdsUtilityRegistry->add_ads_utilities(
83 0           "ReportDownloaderFile");
84              
85 0           my $gzip_support = $self->__set_gzip_header();
86 0           my $request = $self->get___http_request();
87 0           my $format = $self->get_download_format();
88 0           my $start_time = [gettimeofday()];
89 0           my $response;
90 0           ($file_path) = glob($file_path);
91 0 0         if (!$gzip_support) {
92             # If not gzip support then we can stream directly to a file.
93 0           $response = $self->get___user_agent()->request($request, $file_path);
94 0           $response = $self->__check_response($response, $start_time);
95             } else {
96 0           my $mode = ">:utf8";
97 0 0         if ($format =~ /^GZIPPED|PDF/) {
98             # Binary format can't dump as UTF8.
99 0           $mode = ">";
100             }
101 0 0         open(FH, $mode, $file_path)
102             or warn "Can't write to '$file_path': $!";
103 0           $response = $self->get___user_agent()->request($request);
104 0           $response = $self->__check_response($response, $start_time);
105 0 0         if (ref $response eq "Google::Ads::Common::ReportDownloadError") {
106 0           return $response;
107             }
108             # Need to decode in a file.
109 0           print FH $response->decoded_content();
110 0           close FH;
111             }
112 0           return $response;
113             }
114              
115             # Use this method to process results as a stream. For each chunk of data
116             # returned, the content_callback will be invoked with two arguments:
117             # $data - the chunk of data
118             # $response - the HTTP::Response
119             # If the report fails then returns a ReportDownloadError. Otherwise, returns
120             # the HTTP::Response object.
121             sub process_contents {
122 0     0 1   my ($self, $content_callback) = @_;
123              
124 0           Google::Ads::Common::Utilities::AdsUtilityRegistry->add_ads_utilities(
125             "ReportDownloaderStream");
126              
127             # Do not set the gzip header. If it is set then $content_callback will
128             # get compressed data and we don't want clients to have to deal with
129             # inflating the data.
130 0           my $request = $self->get___http_request();
131 0           my $user_agent = $self->get___user_agent();
132 0           my $start_time = [gettimeofday()];
133 0           my $response = $user_agent->request($request, $content_callback);
134 0           $response = $self->__check_response($response, $start_time);
135 0           return $response;
136             }
137              
138             # Checks the response's status code. If OK, then returns the HTTPResponse.
139             # Otherwise, returns a new ReportDownloadError.
140             sub __check_response {
141 0     0     my ($self, $response, $start_time) = @_;
142 0           my $is_successful = 0;
143 0           my $report_download_error;
144             my $return_val;
145              
146 0 0         if ($response->code == HTTP_OK) {
147 0           $is_successful = 1;
148 0           $return_val = $response;
149             } else {
150 0 0         if ($response->code == HTTP_BAD_REQUEST) {
151 0           $report_download_error = $self->__extract_xml_error($response);
152             } else {
153 0           $report_download_error = Google::Ads::Common::ReportDownloadError->new({
154             response_code => $response->code,
155             response_message => $response->message
156             });
157             }
158 0           $return_val = $report_download_error;
159             }
160             # Log request and response information before returning the result.
161 0           $self->__log_report_request_response($response, $is_successful,
162             $report_download_error, tv_interval($start_time));
163 0           return $return_val;
164             }
165              
166             # Sets the header and updates the user agent for gzip support if the
167             # environment supports gzip compression.
168             sub __set_gzip_header {
169 0     0     my ($self) = @_;
170 0           my $user_agent = $self->get___user_agent();
171              
172 0           my $can_accept = HTTP::Message::decodable;
173 0           my $gzip_support = $can_accept =~ /gzip/i;
174              
175             # Setting HTTP user-agent and gzip compression.
176 0           $user_agent->default_header("Accept-Encoding" => scalar $can_accept);
177              
178             # Set the header for gzip support.
179 0 0         $user_agent->agent(
180             $self->get_client()->get_user_agent() . ($gzip_support ? " gzip" : ""));
181 0           return $gzip_support;
182             }
183              
184             # Returns a new ReportDownloadError containing the error details of the
185             # failed HTTP::Response.
186             sub __extract_xml_error {
187 0     0     my ($self, $response) = @_;
188 0           my $ref =
189             XML::Simple->new()->XMLin($response->decoded_content(), ForceContent => 1);
190              
191             return Google::Ads::Common::ReportDownloadError->new({
192             response_code => $response->code,
193             response_message => $response->message,
194             type => $ref->{ApiError}->{type}->{content},
195             field_path => $ref->{ApiError}->{fieldPath}->{content}
196             ? $ref->{ApiError}->{fieldPath}->{content}
197             : "",
198             trigger => $ref->{ApiError}->{trigger}->{content}
199             ? $ref->{ApiError}->{trigger}->{content}
200 0 0         : ""
    0          
201             });
202             }
203              
204             # Logs the report request, response, and stats.
205             sub __log_report_request_response {
206 0     0     my ($self, $response, $is_successful, $error_message, $elapsed_seconds) = @_;
207              
208 0           my $client = $self->get_client();
209 0           my $request = $self->get___http_request();
210              
211             # Always log the request stats to the AdWordsAPI logger.
212 0           my $auth_handler = $client->_get_auth_handler();
213              
214 0 0         my $request_stats = Google::Ads::AdWords::RequestStats->new({
215             server => $client->get_alternate_url(),
216             client_id => $client->get_client_id(),
217             service_name => $request->uri,
218             method_name => $request->method,
219             is_fault => !$is_successful,
220             response_time => int(($elapsed_seconds * 1000) + 0.5),
221             fault_message => (!$is_successful) ? $response->message : ""
222             });
223 0           $client->_push_new_request_stats($request_stats);
224 0           Google::Ads::AdWords::Logging::get_awapi_logger->info($request_stats);
225              
226             # Log the request.
227 0 0         if ($request) {
228             # Log the full request:
229             # To WARN if the request failed OR
230             # To INFO if the request succeeded
231 0           my $request_string = $request->as_string("\n");
232             # Remove sensitive information from the log message.
233 0           foreach my $header (SCRUBBED_HEADERS) {
234 0           $request_string =~ s!(\n$header):(.*)\n!$1: REDACTED\n!;
235             }
236 0           my $log_message = sprintf(
237             "Outgoing request:\n%s",
238             $request_string
239             );
240 0 0         Google::Ads::AdWords::Logging::get_soap_logger->log(
241             $is_successful ? $INFO : $WARN, $log_message);
242             }
243              
244             # Log the response.
245 0 0         if ($response) {
246             # Log:
247             # To WARN if the request failed OR
248             # To INFO (status and message only)
249 0           my $response_string = $response->headers_as_string("\n");
250             # Remove sensitive information from the log message.
251 0           foreach my $header (SCRUBBED_HEADERS) {
252 0           $response_string =~ s!(\n$header):(.*)\n!$1: REDACTED\n!;
253             }
254 0 0         my $log_message = sprintf(
255             "Incoming %s report response with status code %s and message '%s'\n%s" .
256             "REDACTED REPORT DATA",
257             $is_successful ? 'successful' : 'failed',
258             $response->code, $response->message,
259             $response_string
260             );
261              
262 0 0         if ($is_successful) {
263 0           Google::Ads::AdWords::Logging::get_soap_logger->info($log_message);
264             } else {
265 0 0         if (ref $error_message eq "Google::Ads::Common::ReportDownloadError") {
    0          
266 0           $log_message = $log_message .
267             sprintf(
268             ": An error has occurred of type '%s', triggered by '%s'",
269             $error_message->get_type(),
270             $error_message->get_trigger());
271             } elsif ($error_message) {
272 0           $log_message = $log_message . ': ' . $error_message;
273             }
274 0           Google::Ads::AdWords::Logging::get_soap_logger->logwarn($log_message);
275             }
276             }
277             }
278              
279             1;
280              
281             =pod
282              
283             =head1 NAME
284              
285             Google::Ads::Common::ReportDownloadHandler
286              
287             =head1 DESCRIPTION
288              
289             Represents a report response from the AdWords API.
290              
291              
292             =head2 PROPERTIES
293              
294             The following properties may be accessed using get_PROPERTY methods:
295              
296             =over
297              
298             =item * client
299              
300             A reference to a Google::Ads::AdWords::Client.
301              
302             =item * __user_agent (Private)
303              
304             A reference to an LWP::UserAgent.
305              
306             =item * __http_request (Private)
307              
308             A reference to an HTTP::Request.
309              
310             =item * download_format
311              
312             The download format of the request.
313              
314             =back
315              
316              
317             =head1 METHODS
318              
319             =head2 new
320              
321             Constructor. The following data structure may be passed to new():
322              
323             { # Google::Ads::Common::ReportDownloadHandler
324             client => $response, # A ref to a Google::Ads::AdWords::Client object
325             __user_agent => $user_agent, # A ref to an LWP::UserAgent
326             __http_request => $request, # A ref to an HTTP::Request object
327             download_format => $download_format, # The download format for the request
328             },
329              
330             =head1 METHODS
331              
332             =head2 get_as_string
333              
334             Issues the report request to the AdWords API and returns the report contents
335             as a string.
336              
337             =head3 Returns
338              
339             The report contents as a string if the request is successful. Otherwise, returns
340             a L. Check for failures by evaluating
341             the return value in a boolean context, where a
342             L will always evaluate to false.
343              
344             =head3 Exceptions
345              
346             Returns a L if the report request
347             fails.
348              
349             =head2 save
350              
351             Issues the report request to the AdWords API and saves the report contents
352             to a file.
353              
354             =head3 Parameters
355              
356             =over
357              
358             =item *
359              
360             The destination file for the report contents.
361              
362             =back
363              
364             =head3 Returns
365              
366             The report contents as a string if the request is successful. Otherwise, returns
367             a L. Check for failures by
368             evaluating the return value in a boolean context, where a
369             L will always evaluate to false.
370              
371             =head3 Exceptions
372              
373             Returns a L if the report request
374             fails.
375              
376             =head2 process_contents
377              
378             Issues the report request to the AdWords API and invokes a callback for each
379             chunk of content received. Use this method to process the report contents as
380             a stream.
381              
382             =head3 Parameters
383              
384             =over
385              
386             =item *
387              
388             A content_callback that will be invoked for each chunk of data returned
389             by the report request. Each invocation will be passed two arguments:
390              
391             =over
392              
393             =item *
394              
395             The chunk of data
396              
397             =item *
398              
399             The HTTP::Response
400              
401             =back
402              
403             =back
404              
405             =head3 Returns
406              
407             An HTTP::Response if the request is successful. Otherwise, returns
408             a L. Check for failures by
409             evaluating the return value in a boolean context, where a
410             L will always evaluate to false.
411              
412             =head3 Exceptions
413              
414             Returns a L if the report request
415             fails.
416              
417             =cut
418