File Coverage

blib/lib/Yahoo/Marketing/Service.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Yahoo::Marketing::Service;
2             # Copyright (c) 2009 Yahoo! Inc. All rights reserved.
3             # The copyrights to the contents of this file are licensed under the Perl Artistic License (ver. 15 Aug 1997)
4              
5 4     4   22938 use strict; use warnings;
  4     4   8  
  4         119  
  4         19  
  4         7  
  4         110  
6              
7 4     4   19 use base qw/ Class::Accessor::Chained Yahoo::Marketing /;
  4         12  
  4         2802  
8              
9 4     4   12982 use Carp;
  4         8  
  4         275  
10 4     4   3161 use YAML qw/DumpFile LoadFile Dump/;
  4         36241  
  4         301  
11 4     4   4302 use XML::XPath;
  0            
  0            
12             use bytes;
13             use SOAP::Lite on_action => sub { sprintf '' };
14             use Scalar::Util qw/ blessed /;
15             use Cache::FileCache;
16             use Encode qw/is_utf8 _utf8_on/;
17             use Yahoo::Marketing::ApiFault;
18              
19             our $service_data;
20              
21             __PACKAGE__->mk_accessors( qw/ username
22             password
23             license
24             master_account
25             account
26             on_behalf_of_username
27             on_behalf_of_password
28             endpoint
29             use_wsse_security_headers
30             use_location_service
31             last_command_group
32             remaining_quota
33             uri
34             version
35             cache
36             cache_expire_time
37             fault
38             immortal
39             / );
40              
41             sub simple_type_exceptions {
42             return (qw/
43             AccountStatus
44             AccountType
45             AdGroupForecastMatchType
46             AdGroupStatus
47             AdStatus
48             BasicReportType
49             BidStatus
50             BulkDownloadStatus
51             BulkFeedbackFileType
52             BulkFileType
53             BulkUploadStatus
54             CarrierStatus
55             CampaignStatus
56             Continent
57             ConversionMetric
58             DateRange
59             DayOfTheWeek
60             DistanceUnits
61             DuplicateCampaignOption
62             EditorialStatus
63             ErrorKeyType
64             FileFormat
65             FileOutputType
66             ForecastMatchType
67             Gender
68             Importance
69             KeywordForecastMatchType
70             KeywordStatus
71             MasterAccountStatus
72             NotParticipatingInMarketplaceReason
73             OptInReporting
74             OutputFile
75             ParticipationStatus
76             RangeNameType
77             ReportStatus
78             ResponseStatusCodeType
79             SignupStatus
80             SpendCapTactic
81             SpendCapType
82             Status
83             TacticType
84             TargetableLevel
85             TargetingPremiumType
86             UnderAgeFilter
87             UserStatus
88             /);
89             }
90              
91              
92             sub new {
93             my ( $class, %args ) = @_;
94              
95             # some defaults
96             $args{ use_wsse_security_headers } = 1 unless exists $args{ use_wsse_security_headers };
97             $args{ use_location_service } = 1 unless exists $args{ use_location_service };
98             $args{ cache_expire_time } = '1 day' unless exists $args{ cache_expire_time };
99             $args{ version } = 'V7' unless exists $args{ version };
100              
101             $args{ uri } = 'http://marketing.ews.yahooapis.com/V7'
102             unless exists $args{ uri };
103              
104             my $self = bless \%args, $class;
105            
106             # setup our cache
107             if( $self->cache ){
108             croak "cache argument not a Cache::Cache object!"
109             unless ref $self->cache and $self->cache->isa( 'Cache::Cache' );
110             }else{
111             $self->cache( Cache::FileCache->new );
112             }
113              
114             return $self;
115             }
116              
117             sub wsdl_init {
118             my $self = shift;
119              
120             $self->_parse_wsdl;
121             return;
122             }
123              
124             sub parse_config {
125             my ( $self, %args ) = @_;
126              
127             $args{ path } = 'yahoo-marketing.yml' unless defined $args{ path };
128             $args{ section } = 'default' unless defined $args{ section };
129              
130             my $config = LoadFile( $args{ path } );
131              
132             foreach my $config_setting ( qw/ username password master_account license endpoint uri version / ){
133             my $value = $config->{ $args{ 'section' } }->{ $config_setting };
134             croak "no configuration value found for $config_setting in $args{ path }\n"
135             unless $value;
136             $self->$config_setting( $value );
137             }
138              
139             foreach my $config_setting ( qw/ default_account default_on_behalf_of_username default_on_behalf_of_password / ){
140             my $value = $config->{ $args{ 'section' } }->{ $config_setting };
141             my $setting_name = $config_setting;
142             $setting_name =~ s/^default_//;
143             # Maybe we should let the default overwrite ???
144             $self->$setting_name( $value ) if defined $value and not defined $self->$setting_name;
145             }
146              
147             return $self;
148             }
149              
150             sub _service_name {
151             my $self = shift;
152             return (split /::/, ref $self)[-1] ;
153             }
154              
155              
156             sub _proxy_url {
157             my $self = shift;
158             return $self->_location.'/'.$self->_service_name;
159             }
160              
161              
162             sub _location {
163             my $self = shift;
164              
165             unless( $self->use_location_service ){
166             return $self->endpoint.'/'.$self->version;
167             }
168              
169             my $locations = $self->cache->get( 'locations' );
170              
171             if( $locations
172             and $locations->{ $self->version }->{ $self->endpoint }
173             and $locations->{ $self->version }->{ $self->endpoint }->{ $self->master_account } ){
174             return $locations->{ $self->version }->{ $self->endpoint }->{ $self->master_account };
175             }
176              
177             my $som = $self->_soap( $self->endpoint
178             .'/'
179             .$self->version
180             .'/LocationService'
181             )
182             ->getMasterAccountLocation( $self->_headers( no_account => 1 ) );
183              
184             if( $som->fault ){
185             $self->fault( $self->_get_api_fault_from_som( $som ) );
186             $self->_die_with_soap_fault( $som ) unless $self->immortal;
187             warn "we could not determine the correct location endpoint, trying with default";
188             return $self->endpoint.'/'.$self->version;
189             }
190            
191              
192             my $location = $som->valueof( '/Envelope/Body/getMasterAccountLocationResponse/out' );
193              
194             die "failed to get Master Account Location!" unless $location;
195              
196             $location .= '/'.$self->version;
197              
198             $locations->{ $self->version }->{ $self->endpoint }->{ $self->master_account } = $location ;
199              
200             $self->cache->set( 'locations', $locations, $self->cache_expire_time );
201              
202             return $location;
203             }
204              
205             sub _get_api_fault_from_som {
206             my ( $self, $som ) = @_;
207              
208             my @faults = ( defined $som->faultdetail
209             ? map { Yahoo::Marketing::ApiFault->_new_from_hash( $_ ) }
210             ( ref $som->faultdetail->{ApiFault} eq 'ARRAY'
211             ? @{ $som->faultdetail->{ApiFault} }
212             : ( $som->faultdetail->{ApiFault} )
213             )
214             : Yahoo::Marketing::ApiFault->_new_from_hash( { code => 'none', message => 'none', } )
215             )
216             ;
217             warn "warning, found more than 1 fault! This is likely a bug in the web service itself, please report it"
218             if @faults > 1;
219              
220             return $faults[0];
221             }
222              
223              
224             sub _die_with_soap_fault {
225             my ( $self, $som ) = @_;
226              
227             croak(<
228             SOAP FAULT!
229              
230             String: @{[ $som->faultstring ]}
231              
232             Code: @{[ $self->fault->code ]}
233             Message: @{[ $self->fault->message ]}
234              
235             ENDFAULT
236             }
237              
238              
239             # not using memoize yet
240             our %_soap;
241             sub _soap {
242             my ( $self, $endpoint ) = @_;
243              
244             $endpoint ||= $self->_proxy_url;
245              
246             $_soap{ $endpoint }
247             ||= SOAP::Lite->proxy( $endpoint )
248             ->ns( $self->uri, 'ysm' )
249             ->default_ns( $self->uri )
250             ;
251              
252             return $_soap{ $endpoint };
253             }
254              
255             our $AUTOLOAD;
256             sub AUTOLOAD {
257             my $method = $AUTOLOAD;
258             $method =~ s/(.+)\:\://g;
259             my $package = $1;
260              
261             my $self = shift;
262              
263             return if $method eq 'DESTROY' ;
264              
265             return $self->_process_soap_call( $package, $method, @_ );
266             }
267              
268              
269             sub _process_soap_call {
270             my ( $self, $package, $method, @args ) = @_;
271              
272             $self->wsdl_init unless defined $service_data->{ $self->_wsdl };
273              
274             $self->fault(undef);
275              
276             # can't pull @args in as a hash, because we need to preserve the order
277              
278             my @soap_args;
279             while( my $key = shift @args ){
280             my $value = shift @args;
281             push @soap_args, $self->_serialize_argument( $method, $key => $value );
282             }
283              
284             # since we have our own _escape_xml_baddies, here we override SOAP::Serializer::as_string.
285             # see comments in _escape_xml_baddies.
286             local *SOAP::Serializer::as_string = \&Yahoo::Marketing::Service::_as_string;
287             # SOAP::Serializer treat utf8 as base64, we need to override to set as_string.
288             # $self->_soap->typelookup->{utf8String} = [8, sub { $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/}, 'as_string'];
289              
290             my $som = $self->_soap->$method( @soap_args, $self->_headers );
291              
292             if( $som->fault ){
293             $self->fault( $self->_get_api_fault_from_som( $som ) );
294             $self->_die_with_soap_fault( $som ) unless $self->immortal;
295             return; # only reach this if we didn't die above
296             }
297              
298             $self->_set_quota_from_som( $som );
299             return $self->_parse_response( $som, $method.'Response' );
300             }
301              
302             sub _set_quota_from_som {
303             my ( $self, $som ) = @_;
304              
305             my $remaining_quota = $som->valueof( '/Envelope/Header/remainingQuota' );
306             my $command_group = $som->valueof( '/Envelope/Header/commandGroup' );
307              
308             $self->last_command_group( $command_group );
309             $self->remaining_quota( $remaining_quota );
310             return;
311             }
312              
313             sub _parse_response {
314             my ( $self, $som, $method ) = @_;
315              
316             if( my $result = $som->valueof( "/Envelope/Body/$method/" ) ){
317              
318             # catch empty string responses
319             return if ( not defined $result->{ out } ) or ( defined $result->{ out } and $result->{ out } eq '' );
320              
321             my @return_values;
322              
323             my $type = $self->_complex_types( $method, 'out' );
324              
325             my @values;
326             if( $type =~ /ArrayOf/ ){
327             my $element_type = $self->_complex_types( $type );
328             @values = ref( $result->{ out }->{ $element_type } ) eq 'ARRAY'
329             ? map { $self->_deserialize( $method, $_, $element_type ) } @{ $result->{ out }->{ $element_type } }
330             : ( $self->_deserialize( $method, $result->{ out }->{ $element_type }, $element_type ) )
331             ;
332             }else{
333             @values = $self->_deserialize( $method, $result->{ out }, $type );
334             }
335              
336             die 'Unable to parse response!' unless @values;
337              
338             return wantarray
339             ? @values
340             : $values[0];
341              
342             }
343              
344             return 1; # no output, but seemed succesful
345             }
346              
347              
348             sub _deserialize {
349             my ( $self, $method, $hash, $type ) = @_;
350              
351             my @return_values;
352              
353             my $obj;
354              
355             if( ref $hash eq 'ARRAY' ){
356             return map { $self->_deserialize( $method, $_, $type ) } @{ $hash };
357             }elsif( $type =~ /ArrayOf(.*)/ ){
358             my $element_type = $1;
359             return [ map { $self->_deserialize( $method, $_, $element_type ) } ( ref $hash eq 'ARRAY' ? @{ $hash } : values %$hash ) ];
360             }elsif( $type !~ /^xsd:|^[Ss]tring$|^[Ii]nt$|^[Ll]ong$|^[Dd]ouble|^Continent$/
361             and ! grep { $type =~ /^(tns:)?$_$/ } $self->simple_type_exceptions ){
362              
363             $type =~ s/^tns://;
364              
365             # pull it in
366             my $pkg = $self->_class_name;
367             my $class = ($pkg).ucfirst( $type );
368             eval "require $class";
369              
370             die "whoops, couldn't load $class: $@" if $@;
371              
372             $obj = $class->new;
373             }elsif( ref $hash ne 'HASH' ){
374             return $hash;
375             }else{ # this should never be reached
376             confess "Please send this stack trace to the module author.\ntype = $type, hash = $hash";
377             }
378              
379             foreach my $key ( keys %$hash ){
380             if( not ref $hash->{ $key } ){
381             $obj->$key( $hash->{ $key } );
382             }elsif( ref $hash->{ $key } eq 'ARRAY' ){ # better have an array arguement mapping
383             my $type = $self->_complex_type( $type, $key );
384              
385             return [ map { $self->_deserialize( $method, $_, $type ) } @{ $hash->{ $key } } ];
386             }elsif( ref $hash->{ $key } eq 'HASH' ){
387             my $type = $self->_complex_types( $type, $key );
388              
389             # special case for array types returning as just a hash with a single element. Annoying.
390             if( $type =~ /^ArrayOf/ ){
391             $type = $self->_complex_types( $method, $type );
392             $obj->$key( [ $self->_deserialize( $method, $hash->{ $key }->{ (keys %{ $hash->{ $key } })[0] }, $type ) ] );
393             next;
394             }
395            
396             $obj->$key( $self->_deserialize( $method, $hash->{ $key }, $type ) );
397             }else{
398             warn "can't handle $key in response yet ( $hash->{ $key } )\n";
399             }
400             }
401              
402             push @return_values, $obj;
403              
404             return wantarray
405             ? @return_values
406             : $return_values[0]
407             ;
408             }
409              
410              
411             sub _headers {
412             my ( $self, %args ) = @_;
413              
414             confess "must set username and password"
415             unless defined $self->username and defined $self->password;
416              
417             return ( $self->_login_headers,
418             SOAP::Header->name('license')
419             ->value( $self->license )
420             ->uri( $self->uri )
421             ->prefix('')
422             ,
423             ( $self->_add_master_account_to_header and not $args{ no_master_account } )
424             ? SOAP::Header->name('masterAccountID')
425             ->type('string')
426             ->value( $self->master_account )
427             ->uri( $self->uri )
428             ->prefix('')
429             : ()
430             ,
431             ( $self->_add_account_to_header and not $args{ no_account } )
432             ? SOAP::Header->name('accountID')
433             ->type('string')
434             ->value( $self->account )
435             ->uri( $self->uri )
436             ->prefix('')
437             : ()
438             ,
439             $self->on_behalf_of_username
440             ? SOAP::Header->name('onBehalfOfUsername')
441             ->type('string')
442             ->value( $self->on_behalf_of_username )
443             ->uri( $self->uri )
444             ->prefix('')
445             : ()
446             ,
447             $self->on_behalf_of_password
448             ? SOAP::Header->name('onBehalfOfPassword')
449             ->type('string')
450             ->value( $self->on_behalf_of_password )
451             ->uri( $self->uri )
452             ->prefix('')
453             : ()
454             ,
455             );
456             }
457              
458             sub _add_account_to_header { return 0; } # default to false
459              
460             sub _add_master_account_to_header { return 1; } # default to true
461              
462              
463             sub _login_headers {
464             my ( $self ) = @_;
465             return $self->use_wsse_security_headers
466             ? ( SOAP::Header->name( 'Security' )
467             ->value(
468             \SOAP::Header->name( 'UsernameToken' )
469             ->value( [ SOAP::Header->name('Username')
470             ->value( $self->username )
471             ->prefix('wsse')
472             ,
473             SOAP::Header->name('Password')
474             ->value( $self->password )
475             ->prefix('wsse')
476             ,
477             ]
478             )
479             ->prefix( 'wsse' )
480             )
481             ->prefix( 'wsse' )
482             ->uri( 'http://schemas.xmlsoap.org/ws/2002/04/secext' )
483             ,
484             )
485             : (
486             SOAP::Header->name('username')
487             ->value( $self->username )
488             ->uri( $self->uri )
489             ->prefix('')
490             ,
491             SOAP::Header->name('password')
492             ->value( $self->password )
493             ->uri( $self->uri )
494             ->prefix('')
495             ,
496             );
497             }
498              
499             sub clear_cache {
500             my $self = shift;
501             $self->cache->clear;
502             delete $service_data->{ $self->_wsdl }
503             if $service_data and $self->_wsdl_components_are_defined;
504             return $self;
505             }
506              
507             sub purge_cache {
508             my $self = shift;
509             $self->cache->purge;
510             delete $service_data->{ $self->_wsdl }
511             if $service_data and $self->_wsdl_components_are_defined;
512             return $self;
513             }
514              
515             sub _parse_wsdl {
516             my ( $self, ) = @_;
517              
518             if( my $wsdl_data = $self->cache->get( $self->_wsdl ) ){
519             $service_data->{ $self->_wsdl } = $wsdl_data;
520             return;
521             }
522              
523             my $xpath = XML::XPath->new(
524             xml => SOAP::Schema->new(schema_url => $self->_wsdl )->access
525             );
526              
527             foreach my $node ( $xpath->find( q{/wsdl:definitions/wsdl:types/xsd:schema/* } )->get_nodelist ){
528             my $name = $node->getName;
529             if( $name eq 'xsd:complexType' ){
530             $self->_parse_complex_type( $node, $xpath );
531             }elsif( $node->getAttribute('name') and ($node->getAttribute('name') =~ /Response(Type)?$/) ){
532             $self->_parse_response_type( $node, $xpath );
533             }else{
534             $self->_parse_request_type( $node, $xpath );
535             }
536             }
537              
538             $self->cache->set( $self->_wsdl, $service_data->{ $self->_wsdl }, $self->cache_expire_time );
539             return;
540             }
541              
542             sub _parse_request_type {
543             my ( $self, $node, $xpath ) = @_;
544             my $type_name = $node->getAttribute( 'name' );
545              
546             return unless $type_name;
547              
548             my $def = $xpath->find( qq{/wsdl:definitions/wsdl:types/xsd:schema/xsd:element[\@name='$type_name']/xsd:complexType/xsd:sequence/xsd:element} );
549              
550             return unless $def;
551              
552             foreach my $def_node ( $def->get_nodelist ){
553              
554             my $name = $def_node->getAttribute( 'name' );
555             my $type = $def_node->getAttribute( 'type' );
556              
557             #warn "req setting type_map->$type_name->_name = $name";
558             $service_data->{ $self->_wsdl }->{ type_map }->{ $type_name }->{ _name } = $name ;
559             #warn "req setting type_map->$type_name->$name = $type";
560             $service_data->{ $self->_wsdl }->{ type_map }->{ $type_name }->{ $name } = $type ;
561             }
562              
563             return;
564             }
565              
566             sub _parse_response_type {
567             my ( $self, $node, $xpath ) = @_;
568             my $type_name = $node->getAttribute( 'name' );
569             $type_name =~ s/(^tns:)|(^xsd:)//;
570              
571             my $def = $xpath->find( qq{/wsdl:definitions/wsdl:types/xsd:schema/xsd:element[\@name='$type_name']/xsd:complexType/xsd:sequence/xsd:element[\@name='out']} );
572             return unless $def;
573              
574             my $def_node = ($def->get_nodelist)[0]; # there's always just one
575              
576             ( my $name = $def_node->getAttribute( 'name' ) ) =~ s/^tns://;
577             ( my $type = $def_node->getAttribute( 'type' ) ) =~ s/^tns://;
578              
579             #warn "res setting type_map->$type_name->_name = $name";
580             $service_data->{ $self->_wsdl }->{ type_map }->{ $type_name }->{ _name } = $name ;
581             #warn "res setting type_map->$type_name->$name = $type";
582             $service_data->{ $self->_wsdl }->{ type_map }->{ $type_name }->{ $name } = $type ;
583              
584             return;
585             }
586              
587              
588              
589             sub _parse_complex_type {
590             my ( $self, $node, $xpath ) = @_;
591             my $element_name = $node->getAttribute( 'name' );
592             my $type_name = $element_name;
593              
594             my $def = $xpath->find( qq{/wsdl:definitions/wsdl:types/xsd:schema/xsd:complexType[\@name='$type_name']/xsd:sequence/xsd:element} );
595             die "unable to get definition for $type_name" unless $def;
596              
597             foreach my $complex_type_node ( $def->get_nodelist ) {
598             my $name = $complex_type_node->getAttribute('name');
599             ( my $type = $complex_type_node->getAttribute('type') ) =~ s/^tns://;
600              
601             #warn "cpt setting type_map->$type_name->_name = $name";
602             $service_data->{ $self->_wsdl }->{ type_map }->{ $type_name }->{ _name } = $name ;
603             #warn "cpt setting type_map->$type_name->$name = ".( $type =~ /^xsd:/ ? $type : 'tns:'.$type );
604             $service_data->{ $self->_wsdl }->{ type_map }->{ $type_name }->{ $name } = $type =~ /^xsd:/ ? $type : 'tns:'.$type
605             }
606              
607             return;
608             }
609              
610             sub _wsdl_components_are_defined {
611             my $self = shift;
612              
613             return defined $self->endpoint
614             and defined $self->version
615             and defined $self->_service_name
616             ;
617             }
618              
619             sub _wsdl {
620             my $self = shift;
621              
622             return $self->endpoint.'/'.$self->version.'/'.$self->_service_name.'?wsdl';
623             }
624              
625              
626              
627              
628             sub _complex_types {
629             my ( $self, $complex_type, $name ) = @_;
630              
631             my $return;
632              
633             if( @_ == 2 ){ # no $name
634             $return = $service_data->{ $self->_wsdl }->{ type_map }->{ $complex_type }->{_name};
635              
636             }elsif( exists $service_data->{ $self->_wsdl }->{ type_map }->{ $complex_type }->{ $name } ){
637             $return = $service_data->{ $self->_wsdl }->{ type_map }->{ $complex_type }->{ $name } ;
638             }
639              
640             #use Carp qw/cluck/; cluck("\n\n\n\n\n\n\n\n\n\n*****************************************************************\n _complex_types( $complex_type, $name ) returning $return");
641              
642             return $return;
643             }
644              
645              
646             sub _class_name {
647             __PACKAGE__ =~ /^(.+)Service/;
648             return $1;
649             }
650              
651             sub _escape_xml_baddies {
652             my ( $self, $input ) = @_;
653             return unless defined $input;
654             # trouble with HTML::Entities::encode_entities is it will happily double encode things
655             # SOAP::Lite::encode_data also appears to have this problem
656              
657             my $on_utf8 = is_utf8($input);
658             $input =~ s/&(?![#\w]+;)/&/g; # encode &, but not the & in already encoded string (&)
659              
660             # if string is already wrapped , leave it as is. multi-line allowed by /s modifier.
661             if ( $input =~ /^<\!\[CDATA\[(.+)\]\]>$/s ) {
662             return $input;
663             }
664             # otherwise, encode < and >
665             $input =~ s/
666             $input =~ s/>/>/g; # encode >
667              
668             _utf8_on($input) if $on_utf8;
669              
670             return $input;
671             }
672              
673             sub _as_string {
674             # sub SOAP::Serializer::as_string {
675             my $self = shift;
676             my($value, $name, $type, $attr) = @_;
677             die "String value expected instead of @{[ref $value]} reference\n" if ref $value;
678             return [$name, {'xsi:type' => 'xsd:string', %$attr}, $value];
679             }
680              
681             sub _serialize_argument {
682             my ( $self, $method, $name, $value, @additional_values ) = @_;
683              
684             # there are three major decision paths here:
685              
686             # if we get multiple values (as an array reference)
687             # serialize each individually and the serialize the whole, then return
688              
689             # if we get multiple values (as an array)
690             # return an array of each serialized individually
691              
692             # if we get a just one non-array ref value, serialize it
693             # using ->_serialize_complex_type if it's blessed
694             # using ->_complex_types if they apply
695              
696             if( ref $value eq 'ARRAY' ){
697             if( my $type_def = $self->_complex_types( $method, $name ) ) { # it's one of those multiple methods
698             ( my $sub_type = $type_def ) =~ s/^tns://;
699            
700             return SOAP::Data->type( $type_def )
701             ->name( $name )
702             ->value( \SOAP::Data->value( $self->_serialize_argument( $name, $self->_complex_types( $sub_type ), @$value ) ) );
703             }
704             }
705              
706             if( scalar @additional_values ){
707             my @return;
708             foreach my $element ( $value, @additional_values ){
709             push @return, $self->_serialize_argument( $method, $name, $element );
710             }
711             return @return;
712             }
713              
714             if( blessed( $value ) and $value->UNIVERSAL::isa( 'Yahoo::Marketing::ComplexType' ) ){
715             my $type = $self->_complex_types( $method, $name );
716             return SOAP::Data->name( $name )
717             ->type( $type )
718             ->value( $self->_serialize_complex_type( $method, $value ) )
719             ;
720             }elsif( my $type = $self->_complex_types( $method, $name ) ){
721             return SOAP::Data->name( $name )
722             ->type( $type )
723             ->value( defined( $value ) ? $self->_escape_xml_baddies( "$value" ) : undef ) # force it stringy for now
724             ;
725             }
726              
727             # special case: RangeNameType needs hack to not use 'string' as type due to apache axis problem on server side.
728             if ( $name eq 'RangeNameType') {
729             return SOAP::Data->name( $name )
730             ->type( 'xsd:enumeration' )
731             ->value( $self->_escape_xml_baddies($value) );
732             }
733              
734             # don't do anything special
735             return SOAP::Data->name( $name )
736             ->type( 'xsd:string' )
737             ->value( $self->_escape_xml_baddies($value) );
738             }
739              
740              
741             sub _serialize_complex_type {
742             my ( $self, $method, $complex_type ) = @_;
743              
744             return \SOAP::Data->value( map { $self->_serialize_argument( $complex_type->_type, $_, $complex_type->$_ )
745             }
746             grep { defined $complex_type->$_ } $complex_type->_user_setable_attributes
747             )
748             ->type( $complex_type->_type )
749             ;
750             }
751              
752              
753              
754             1; # End of Yahoo::Marketing::Service
755              
756             =head1 NAME
757              
758             Yahoo::Marketing::Service - a base class for Service modules
759              
760             =head1 SYNOPSIS
761              
762             This module is a base class for various Service modules (CampaignService,
763             AdGroupService, ForecastService, etc) to inherit from. It should not be used directly.
764              
765             There are some methods common to all Services that are documented below.
766              
767             See also perldoc Yahoo::Marketing::AccountService
768             ...::AdGroupService
769             ...::AdService
770             ...::BasicReportService
771             ...::BidInformationService
772             ...::BudgetingService
773             ...::CampaignService
774             ...::ExcludedWordsService
775             ...::ForecastService
776             ...::KeywordResearchService
777             ...::KeywordService
778             ...::LocationService
779             ...::MasterAccountService
780             ...::UserManagementService
781              
782             Please see the API docs at
783              
784             L
785              
786             for details about what methods are available from each of the Services.
787              
788              
789             =head1 EXPORT
790              
791             No exported functions
792              
793             =head1 METHODS
794              
795             =cut
796              
797             =head2 simple_type_exceptions
798              
799             Return all simple data type exceptions
800              
801             =head2 new
802              
803             Creates a new instance.
804              
805             =head2 username
806              
807             Get/set the username to be used for requests
808              
809             =head2 password
810              
811             Get/set the password to be used for requests
812              
813             =head2 license
814              
815             Get/set the license to be used for requests
816              
817             =head2 version
818              
819             Get/set the version to be used for requests
820              
821             =head2 uri
822              
823             Get/set the URI to be used for requests.
824              
825             Defaults to http://marketing.ews.yahooapis.com/V7
826              
827             =head2 master_account
828              
829             Get/set the master account to be used for requests
830              
831             =head2 account
832              
833             Get/set the account to be used for requests. Not all requests require an account.
834             Any service that deals with Campaigns (or Ad Groups, Ads, or Keywords) requires account
835             to be set.
836              
837             L
838              
839             =head2 immortal
840              
841             If set to a true value, Yahoo::Marketing service objects will not die when a SOAP fault
842             is encountered. Instead, $service->fault will be set to the ApiFault returned in the
843             SOAP response.
844            
845             Defaults to false.
846              
847             =head2 on_behalf_of_username
848              
849             Get/set the onBehalfOfUsername to be used for requests.
850              
851             L
852              
853             =head2 on_behalf_of_password
854              
855             Get/set the onBehalfOfPassword to be used for requests.
856              
857             L
858              
859             =head2 use_wsse_security_headers
860              
861             If set to a true value, requests will use the WSSE headers for authentication. See L
862              
863             Defaults to true.
864              
865             =head2 use_location_service
866              
867             If set to a true value, LocationService will be used to determine the correct endpoint URL based on the account being used.
868              
869             Defaults to true.
870              
871             =head2 cache
872              
873             Allows the user to pass in a Cache::Cache object to be used for cacheing WSDL information and account locations.
874              
875             Defaults to using Cache::FileCache
876              
877             =head2 cache_expire_time
878              
879             Set the amount of time WSDL information should be cached.
880              
881             Defaults to 1 day.
882              
883             =head2 purge_cache
884              
885             Purges all expired items from the cache. See purge() documentation in perldoc Cache::Cache.
886              
887             =head2 clear_cache
888              
889             Clears all items from the cache. See clear() documentation in perldoc Cache::Cache.
890              
891             =head2 last_command_group
892              
893             After a request, this will be set to the name of the last command group used.
894              
895             =head2 remaining_quota
896              
897             After a request, this will be set to the amount of quota associated with the last command group used.
898              
899             =head2 wsdl_init
900              
901             Accesses the appropriate wsdl and parses it to determine how to serialize / deserialize requests and responses. Note that you must have set the endpoint.
902              
903             If you do not call it, calling any soap method on the service will force it to be called.
904              
905             =head2 parse_config
906              
907             Usage:
908             ->parse_config( path => '/path/to/config.yml',
909             section => 'some_section', # for example, 'sandbox'
910             );
911              
912             Defaults:
913             path => 'yahoo-marketing.yml' # in current working directory
914             section => 'default'
915              
916             Attempts to parse the given config file, or yahoo-marketing.ysm in the current
917             directory if no path is specified.
918              
919             parse_config() returns $self, so you can do things like this:
920              
921             my $service = Yahoo::Marketing::CampaignService->new->parse_config();
922              
923             The default config section used is 'default'
924              
925             Note that "default_account", "default_on_behalf_of_username", and "default_on_behalf_of_password" are not required. If present, they will be used to set "account", "on_behalf_of_username", and "on_behalf_of_password" *if* those values have not already been set.
926              
927             See example config file in the EXAMPLES section of perldoc Yahoo::Marketing
928              
929              
930              
931             =cut