File Coverage

blib/lib/Microsoft/AdCenter/Service.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Microsoft::AdCenter::Service;
2             # Copyright (C) 2012 Xerxes Tsang
3             # This program is free software; you can redistribute it and/or modify it
4             # under the terms of Perl Artistic License.
5              
6 986     986   77987 use strict;
  986         2676  
  986         38162  
7 986     986   6709 use warnings;
  986         2206  
  986         35492  
8              
9             =head1 NAME
10              
11             Microsoft::AdCenter::Service - Base class for service client modules.
12              
13             =cut
14              
15             =head1 SYNOPSIS
16              
17             This module is not intended to be used directly. Documentation for each of the service client is in the appropriate module.
18              
19             =cut
20              
21 986     986   8782 use base qw/Class::Accessor::Chained Microsoft::AdCenter/;
  986         2446  
  986         1094728  
22              
23 986     986   1413426 use Data::Dumper;
  986         13832574  
  986         115506  
24 986     986   1307028 use Encode qw/is_utf8 _utf8_on/;
  986         15083554  
  986         136551  
25 986     986   10230 use Scalar::Util qw/blessed/;
  986         2238  
  986         99693  
26 986     986   926207 use SOAP::Lite;
  0            
  0            
27              
28             use Microsoft::AdCenter::Retry;
29             use Microsoft::AdCenter::SOAPFault;
30              
31             __PACKAGE__->mk_accessors(qw/
32             EndPoint
33             RetrySettings
34             /);
35              
36             sub new {
37             my ($class, %args) = @_;
38              
39             my $self = bless {}, $class;
40              
41             $self->EndPoint((defined $args{EndPoint}) ? $args{EndPoint} : $self->_default_location);
42             $self->RetrySettings($args{RetrySettings});
43              
44             my $request_headers = $self->_request_headers_expanded;
45             foreach my $header_name (keys %$request_headers) {
46             if (defined $args{$header_name}) {
47             $self->$header_name($args{$header_name});
48             }
49             }
50              
51             my $namespace_uri = $self->_namespace_uri;
52             my $namespace = $namespace_uri;
53             $namespace =~ s/\/$//;
54             $namespace =~ s/^.*\///;
55              
56             $self->{_soap} = SOAP::Lite
57             ->proxy($self->EndPoint)
58             ->ns($namespace_uri, $namespace);
59              
60             $self->{_namespaces} = {
61             $namespace_uri => $namespace
62             };
63              
64             $self->{_type_category} = {};
65             $self->{_type_namespace} = {};
66             $self->{_type_full_names} = {};
67              
68             return $self;
69             }
70              
71             sub _invoke {
72             my ($self, %args) = @_;
73              
74             my $soap_action = $args{soap_action};
75             my $request_name = $args{request}->{name};
76             my $request_headers = $self->_request_headers;
77             my $request_parameters = $args{request}->{parameters};
78             my $response_name = $args{response}->{name};
79             my $response_headers = $self->_request_headers;
80             my $response_headers_expanded = $self->_response_headers_expanded;
81             my $parameter_values = $args{parameters};
82              
83             # Reset the response headers
84             foreach my $header_name (keys %$response_headers_expanded) {
85             $self->$header_name(undef);
86             }
87              
88             # Setup the SOAP client
89             my $soap = $self->{_soap};
90             $soap->proxy($self->EndPoint);
91             $soap->on_action(sub { $soap_action });
92              
93             # Create request headers
94             my @soap_header;
95             foreach my $header (@$request_headers) {
96             my $header_ns = $header->{namespace};
97             my $header_name = $header->{name};
98             my $header_type = $header->{type};
99             my $type_category = $self->_type_category($header_type);
100             my $header_value = ($type_category eq 'COMPLEX') ? $self->_populate_complex_type($header_type) : $self->$header_name;
101             push @soap_header, $self->_serialize_argument("SOAP::Header", $header_ns, $header_name, $header_value, $header_type, 0);
102             }
103              
104             # Create request body
105             my @soap_body;
106             foreach my $request_parameter (@$request_parameters) {
107             my $request_parameter_ns = $request_parameter->{namespace};
108             my $request_parameter_name = $request_parameter->{name};
109             my $request_parameter_type = $request_parameter->{type};
110             my $parameter_value = $parameter_values->{$request_parameter_name};
111             push @soap_body, $self->_serialize_argument("SOAP::Data", $request_parameter_ns, $request_parameter_name, $parameter_value, $request_parameter_type, 1);
112             }
113              
114             my $retries = 0;
115             while (1) {
116             my $result;
117             eval {
118             # Call the actual web service
119             my $som = $soap->call($request_name, @soap_header, @soap_body);
120              
121             # Check for HTTP 400's errors (which we can't recover from)
122             if ($soap->transport->proxy->code =~ /^4[0-9]{2}$/) {
123             die $soap->transport->proxy->status . " for " . $soap->transport->proxy->endpoint;
124             }
125              
126             # Store the response header values in the service client
127             $self->_store_response_headers($som, $response_headers, $response_headers_expanded);
128              
129             # If it fails, die with a SOAPFault object
130             if ($som->fault) {
131             my $fault = Microsoft::AdCenter::SOAPFault->new
132             ->faultcode($som->faultcode)
133             ->faultstring($som->faultstring);
134              
135             my $faultdetail = $som->faultdetail;
136             if (defined $faultdetail) {
137             if (ref $faultdetail eq 'HASH') {
138             $faultdetail = $self->_deserialize_array($faultdetail);
139             if (scalar(@$faultdetail) == 1) {
140             $fault->detail($faultdetail->[0]);
141             }
142             elsif (scalar(@$faultdetail) > 1) {
143             $fault->detail($faultdetail);
144             }
145             }
146             else {
147             $fault->detail($faultdetail);
148             }
149             }
150             die $fault;
151             }
152              
153             # Parse the response body
154             my $response_body = $som->body;
155             if (defined $response_body) {
156             die "Type mismatch" unless (exists $response_body->{$response_name});
157             $result = $self->_deserialize_complex_type($response_name, $response_body->{$response_name});
158             }
159             };
160             if (my $e = $@) {
161             die $e unless ((defined $self->RetrySettings) && ref($self->RetrySettings) eq 'ARRAY');
162              
163             my $retry_times = undef;
164             my $wait_time = undef;
165             my $scaling_wait_time = undef;
166             my $should_retry = 0;
167              
168             foreach my $retry_settings ( @{$self->RetrySettings} ) {
169             die "Invalid Retry setting" if (ref($retry_settings) ne 'Microsoft::AdCenter::Retry');
170             next unless ($retry_settings->match($e));
171              
172             $should_retry = 1;
173              
174             if ((not defined $retry_times) && (defined $retry_settings->RetryTimes)) {
175             $retry_times = $retry_settings->RetryTimes;
176             }
177              
178             if ((not defined $wait_time) && (defined $retry_settings->WaitTime)) {
179             $wait_time = $retry_settings->WaitTime;
180             }
181              
182             if ((not defined $scaling_wait_time) && (defined $retry_settings->ScalingWaitTime)) {
183             $scaling_wait_time = $retry_settings->ScalingWaitTime;
184             }
185              
186             $retry_settings->Callback->($e) if (defined $retry_settings->Callback);
187             }
188              
189             die $e unless $should_retry;
190              
191             $retry_times = 0 unless (defined $retry_times);
192             $wait_time = 1 unless (defined $wait_time);
193             $scaling_wait_time = 1 unless (defined $scaling_wait_time);
194              
195             if ($retries < $retry_times) {
196             sleep($wait_time + ($wait_time * $retries * ($scaling_wait_time - 1)));
197             $retries++;
198             }
199             else {
200             die $e;
201             }
202             }
203             else {
204             return $result;
205             }
206             }
207             }
208              
209             # Store the response header values in the service client
210             sub _store_response_headers {
211             my ($self, $som, $response_headers, $response_headers_expanded) = @_;
212              
213             my $response_header_values = $som->header;
214             my $expanded_response_headers = {};
215             foreach my $header (@$response_headers) {
216             my $header_name = $header->{name};
217             my $header_value = $response_header_values->{$header_name};
218             next unless defined $header_value;
219             my $header_type = $header->{type};
220             my $header_type_category = $self->_type_category($header_type);
221             if ($header_type_category eq 'COMPLEX') {
222             $self->_expand_complex_type(
223             $self->_deserialize_complex_type($header_type, $header_value),
224             $expanded_response_headers
225             );
226             }
227             elsif ($header_type_category eq 'ARRAY') {
228             $expanded_response_headers->{$header_name} = $self->_deserialize_array($header_value);
229             }
230             else {
231             $expanded_response_headers->{$header_name} = $header_value;
232             }
233             }
234             foreach my $header_name (keys %$response_headers_expanded) {
235             $self->$header_name($expanded_response_headers->{$header_name});
236             }
237             }
238              
239             sub _serialize_argument {
240             my ($self, $type, $namespace, $name, $value, $value_type, $min_occurs) = @_;
241              
242             my $prefix = $self->_get_namespace_prefix($namespace);
243             my $type_namespace = $self->_type_namespace($value_type);
244             my $type_full_name = $self->_type_full_name($value_type);
245             my $object = eval($type . '->type($type_full_name)');
246             $object->prefix($prefix);
247              
248             if (not defined $value) {
249             if ($min_occurs > 0) {
250             $object = $object->attr({'xsi:nil' => "true"});
251             }
252             else {
253             $object = undef;
254             }
255             }
256             elsif (ref($value) eq 'ARRAY') {
257             my %array_types = $self->_array_types;
258             die "Type mismatch" unless (exists $array_types{$value_type});
259             if (scalar(@$value) > 0) {
260             my $element_name = $array_types{$value_type}->{element_name};
261             my $element_type = $array_types{$value_type}->{element_type};
262             my @elements = map { $self->_serialize_argument($type, $type_namespace, $element_name, $_, $element_type, 1) } @$value;
263             $object = $object->value(\eval($type . '->value(@elements)'));
264             }
265             }
266             elsif (blessed($value) and $value->UNIVERSAL::isa('Microsoft::AdCenter::ComplexType')) {
267             die "Type mismatch" unless $value->UNIVERSAL::isa(ref($self) . '::' . $value_type);
268             if ($value_type ne $value->_type_name) {
269             $value_type = $value->_type_name;
270             $type_namespace = $self->_type_namespace($value_type);
271             $type_full_name = $self->_type_full_name($value_type);
272             $object->type($type_full_name);
273             }
274              
275             my @attributes =
276             grep { defined $_ }
277             map { $self->_serialize_argument($type, $type_namespace, $_, $value->$_, $value->_attribute_type($_), $value->_attribute_min_occurs($_)) }
278             $value->_attributes;
279              
280             $object = $object->value(\eval($type . '->value(@attributes)')) if (scalar(@attributes) > 0);
281             }
282             else {
283             $object = $object->value($value);
284             }
285              
286             return undef unless (defined $object);
287             return $object->name($name);
288             }
289              
290             sub _get_namespace_prefix {
291             my ($self, $uri) = @_;
292             my $namespaces = $self->{_namespaces};
293             unless (exists $namespaces->{$uri}) {
294             $namespaces->{$uri} = 'ns' . (scalar(keys %$namespaces));
295             $self->{_soap}->serializer->register_ns($uri, $namespaces->{$uri});
296             }
297             return $namespaces->{$uri};
298             }
299              
300             sub _type_category {
301             my ($self, $type_name) = @_;
302             my $type_category = $self->{_type_category};
303             unless (exists $type_category->{$type_name}) {
304             my %simple_types = $self->_simple_types;
305             my %complex_types = map { $_ => 1 } $self->_complex_types;
306             my %array_types = $self->_array_types;
307             if (exists $simple_types{$type_name}) {
308             $type_category->{$type_name} = 'SIMPLE';
309             }
310             elsif (exists $complex_types{$type_name}) {
311             $type_category->{$type_name} = 'COMPLEX';
312             }
313             elsif (exists $array_types{$type_name}) {
314             $type_category->{$type_name} = 'ARRAY';
315             }
316             else {
317             $type_category->{$type_name} = 'PRIMITIVE';
318             }
319             }
320             return $type_category->{$type_name};
321             }
322              
323             sub _type_namespace {
324             my ($self, $type_name) = @_;
325             my $type_namespace = $self->{_type_namespace};
326             unless (exists $type_namespace->{$type_name}) {
327             my $type_category = $self->_type_category($type_name);
328             if ($type_category eq 'SIMPLE') {
329             my %simple_types = $self->_simple_types;
330             $type_namespace->{$type_name} = $simple_types{$type_name};
331             }
332             elsif ($type_category eq 'COMPLEX') {
333             my $class_name = ref($self) . '::' . $type_name;
334             my $namespace_uri = eval(qq/use ${class_name}; ${class_name}::_namespace_uri()/);
335             $type_namespace->{$type_name} = $namespace_uri;
336             }
337             elsif ($type_category eq 'ARRAY') {
338             my %array_types = $self->_array_types;
339             $type_namespace->{$type_name} = $array_types{$type_name}->{namespace_uri};
340             }
341             else {
342             $type_namespace->{$type_name} = undef;
343             }
344             }
345             return $type_namespace->{$type_name};
346             }
347              
348             sub _type_full_name {
349             my ($self, $type_name) = @_;
350             my $type_full_names = $self->{_type_full_names};
351             unless (exists $type_full_names->{$type_name}) {
352             my $type_namespace = $self->_type_namespace($type_name);
353             if (defined $type_namespace) {
354             my $prefix = $self->_get_namespace_prefix($type_namespace);
355             $type_full_names->{$type_name} = $prefix . ':' . $type_name;
356             }
357             else {
358             $type_full_names->{$type_name} = $type_name;
359             }
360             }
361             return $type_full_names->{$type_name};
362             }
363              
364             sub _create_complex_type {
365             my ($self, $type) = @_;
366             my $class_name = ref($self) . '::' . $type;
367             my $object = eval("use $class_name; $class_name->new");
368             die $@ if $@;
369             return $object;
370             }
371              
372             sub _deserialize_array {
373             my ($self, $value) = @_;
374             return [] unless ((defined $value) && ref($value) eq 'HASH');
375             my @arr;
376             foreach my $element_type (keys %$value) {
377             my $element_type_category = $self->_type_category($element_type);
378             my $element_values = (ref $value->{$element_type} eq 'ARRAY') ? $value->{$element_type} : [$value->{$element_type}];
379             foreach my $element_value (@$element_values) {
380             if ($element_type_category eq 'COMPLEX') {
381             push @arr, $self->_deserialize_complex_type($element_type, $element_value);
382             }
383             elsif ($element_type_category eq 'ARRAY') {
384             push @arr, $self->_deserialize_array($element_value);
385             }
386             else {
387             push @arr, $element_value;
388             }
389             }
390             }
391             return \@arr;
392             }
393              
394             sub _deserialize_complex_type {
395             my ($self, $type, $value) = @_;
396             return unless defined $value;
397             $type = ref($value) if (length(ref($value)) > 0 && ref($value) ne 'HASH');
398             my $object = $self->_create_complex_type($type);
399             foreach my $attribute_name ($object->_attributes) {
400             my $attribute_value = $value->{$attribute_name};
401             next unless defined $attribute_value;
402             my $attribute_type = $object->_attribute_type($attribute_name);
403             my $type_category = $self->_type_category($attribute_type);
404             if ($type_category eq 'COMPLEX') {
405             $attribute_value = $self->_deserialize_complex_type($attribute_type, $attribute_value);
406             }
407             elsif ($type_category eq 'ARRAY') {
408             $attribute_value = $self->_deserialize_array($attribute_value);
409             }
410             $object->$attribute_name($attribute_value);
411             }
412             return $object;
413             }
414              
415             sub _populate_complex_type {
416             my ($self, $type) = @_;
417             my $object = $self->_create_complex_type($type);
418             foreach my $attribute_name ($object->_attributes) {
419             my $attribute_type = $object->_attribute_type($attribute_name);
420             my $type_category = $self->_type_category($attribute_type);
421             my $value = undef;
422             if ($type_category eq 'COMPLEX') {
423             $value = $self->_populate_complex_type($attribute_type);
424             }
425             else {
426             $value = $self->$attribute_name;
427             }
428             $object->$attribute_name($value);
429             }
430             return $object;
431             }
432              
433             sub _expand_complex_type {
434             my ($self, $value, $result) = @_;
435             foreach my $attribute_name ($value->_attributes) {
436             my $attribute_type = $value->_attribute_type($attribute_name);
437             my $type_category = $self->_type_category($attribute_type);
438             if ($type_category eq 'COMPLEX') {
439             $self->_expand_complex_type($value->$attribute_name, $result);
440             }
441             else {
442             $result->{$attribute_name} = $value->$attribute_name;
443             }
444             }
445             }
446              
447             1;