File Coverage

lib/eBay/API/SimpleBase.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package eBay::API::SimpleBase;
2              
3 1     1   882 use strict;
  1         2  
  1         36  
4 1     1   5 use warnings;
  1         2  
  1         38  
5              
6 1     1   392 use XML::LibXML;
  0            
  0            
7             use XML::Simple;
8             use HTTP::Request;
9             use HTTP::Headers;
10             use LWP::UserAgent;
11             use XML::Parser;
12             use URI::Escape;
13             use YAML;
14             use utf8;
15              
16             use base 'eBay::API::Simple';
17              
18             # set the preferred xml simple parser
19             $XML::Simple::PREFERRED_PARSER = 'XML::Parser';
20              
21             our $DEBUG = 0;
22              
23             =head1 NAME
24              
25             eBay::API::SimpleBase - Flexible SDK supporting all eBay web services
26              
27             =head1 DESCRIPTION
28              
29             This is the base class for the eBay::API::Simple::* libraries that provide
30             support for all of eBay's web services. This base class does nothing by itself
31             and must be subclassed to provide the complete web service support.
32              
33             =item L
34              
35             =item L
36              
37             =item L
38              
39             =item L
40              
41             =item L
42              
43             =item L
44              
45             =item L
46              
47             =head1 GET THE SOURCE
48              
49             http://code.google.com/p/ebay-api-simple
50              
51             =head1 PUBLIC METHODS
52              
53             =head2 eBay::API::Simple::{subclass}->new()
54              
55             see subclass for more docs.
56              
57             =item L
58              
59             =item L
60              
61             =item L
62              
63             =item L
64              
65             =item L
66              
67             =item L
68              
69             =item L
70              
71             =cut
72              
73             sub new {
74             my $class = shift;
75             my $api_args = shift;
76              
77             my $self = {};
78             bless( $self, $class );
79            
80             # set some defaults
81             $self->api_config->{siteid} = 0;
82             $self->api_config->{enable_attributes} = 0;
83             $self->api_config->{timeout} = 20 unless defined $api_args->{timeout};
84             $self->api_config->{parallel} = $api_args->{parallel};
85            
86             unless (defined $api_args->{preserve_namespace}) {
87             $self->api_config->{preserve_namespace} = 0;
88             }
89            
90             # set the config args
91             $self->api_config_append( $api_args );
92            
93             return $self;
94             }
95              
96             =head2 execute( $verb, $call_data )
97              
98             Calling this method will prepare the request, execute, and process the response.
99              
100             It is recommended that prepare and process be subclassed rather than this method.
101              
102             =item $verb (required)
103              
104             call verb, i.e. FindItems
105              
106             =item $call_data (required)
107              
108             hashref of call_data that will be turned into xml.
109              
110             =cut
111              
112             sub execute {
113             my $self = shift;
114              
115             $self->prepare( @_ );
116              
117             $self->_execute_http_request();
118              
119             if ( defined $self->{response_content} ) {
120             $self->process();
121             }
122             }
123              
124             =head2 prepare( $verb, $call_data )
125              
126             This is called by execute to prepare the request
127             and may be supplied by the subclass.
128              
129             =cut
130              
131             sub prepare {
132             my $self = shift;
133              
134             $self->{verb} = shift;
135             $self->{call_data} = shift;
136            
137             if ( ! defined $self->{verb} || ! defined $self->{call_data} ) {
138             die "missing verb and call_data";
139             }
140             }
141              
142             =head2 process()
143              
144             This is called by execute to process the response
145             and may be supplied by the subclass.
146              
147             =cut
148              
149             sub process {
150             my $self = shift;
151              
152             if ( $DEBUG ) {
153             print STDERR $self->request_object->as_string();
154             print STDERR $self->response_object->as_string();
155             }
156             }
157              
158             =head2 request_agent
159              
160             Accessor for the LWP::UserAgent request agent
161              
162             =cut
163              
164             sub request_agent {
165             my $self = shift;
166             return $self->{request_agent};
167             }
168              
169             =head2 request_object
170              
171             Accessor for the HTTP::Request request object
172              
173             =cut
174              
175             sub request_object {
176             my $self = shift;
177             return $self->{request_object};
178             }
179              
180             =head2 request_content
181              
182             Accessor for the complete request body from the HTTP::Request object
183              
184             =cut
185              
186             sub request_content {
187             my $self = shift;
188             return $self->{request_object}->as_string();
189             }
190              
191             =head2 response_content
192              
193             Accessor for the HTTP response body content
194              
195             =cut
196              
197             sub response_content {
198             my $self = shift;
199             return $self->{response_content};
200             }
201              
202             =head2 response_object
203              
204             Accessor for the HTTP::Request response object
205              
206             =cut
207              
208             sub response_object {
209             my $self = shift;
210             return $self->{response_object};
211             }
212              
213             =head2 response_dom
214              
215             Accessor for the LibXML response DOM
216              
217             =cut
218              
219             sub response_dom {
220             my $self = shift;
221              
222             if ( ! defined $self->{response_dom} ) {
223             my $parser = XML::LibXML->new();
224             eval {
225             $self->{response_dom} =
226             $parser->parse_string( $self->response_content );
227             };
228             if ( $@ ) {
229             $self->errors_append( { 'parsing_error' => $@ } );
230             }
231             }
232              
233             return $self->{response_dom};
234             }
235              
236             =head2 response_hash
237              
238             Accessor for the hashified response content
239              
240             =cut
241              
242             sub response_hash {
243             my $self = shift;
244              
245             if ( ! defined $self->{response_hash} ) {
246             $self->{response_hash} = XMLin( $self->response_content,
247             forcearray => [],
248             keyattr => []
249             );
250             }
251              
252             return $self->{response_hash};
253             }
254              
255             =head2 response_json
256              
257             Not implemented yet.
258              
259             =cut
260              
261             sub response_json {
262             my $self = shift;
263              
264             if ( ! defined $self->{response_json} ) {
265             $self->{response_json} = ''; # xml2json( $self->{response_content} );
266             }
267              
268             return $self->{response_json};
269             }
270              
271             =head2 nodeContent( $tag, [ $dom ] )
272              
273             Helper for LibXML that retrieves node content
274              
275             =item $tag (required)
276              
277             This is the name of the xml element
278              
279             =item $dom (optional)
280              
281             optionally a DOM object can be passed in. If no DOM object
282             is passed then the main response DOM object is used.
283              
284             =cut
285            
286             sub nodeContent {
287             my $self = shift;
288             my $tag = shift;
289             my $node = shift;
290              
291             $node ||= $self->response_dom();
292              
293             return if ! $tag || ! $node;
294              
295             my $e = $node->getElementsByTagName($tag);
296             if ( defined $e->[0] ) {
297             return $e->[0]->textContent();
298              
299             }
300             else {
301             #print STDERR "no info for $tag\n";
302             return;
303             }
304             }
305              
306             =head2 errors
307              
308             Accessor to the hashref of errors
309              
310             =cut
311              
312             sub errors {
313             my $self = shift;
314             $self->{errors} = {} unless defined $self->{errors};
315             return $self->{errors};
316             }
317              
318             =head2 has_error
319              
320             Returns true if the call contains errors
321              
322             =cut
323              
324             sub has_error {
325             my $self = shift;
326             my $has_error = (keys( %{ $self->errors } ) > 0) ? 1 : 0;
327             return $has_error;
328             }
329              
330             =head2 errors_as_string
331              
332             Returns a string of API errors if there are any.
333              
334             =cut
335              
336             sub errors_as_string {
337             my $self = shift;
338              
339             my @e;
340             for my $k ( keys %{ $self->errors } ) {
341             push( @e, $k . '-' . $self->errors->{$k} );
342             }
343            
344             return join( "\n", @e );
345             }
346              
347             =head1 INTERNAL METHODS
348              
349             =head2 api_config
350              
351             Accessor to a hashref of api config data that will be used to execute
352             the api call.
353              
354             siteid,domain,uri,etc.
355              
356             =cut
357              
358             sub api_config {
359             my $self = shift;
360             $self->{api_config} = {} unless defined $self->{api_config};
361             return $self->{api_config};
362             }
363              
364             =head2 api_config_append( $hashref )
365              
366             This method is used to merge config into the config_api hash
367              
368             =cut
369              
370             sub api_config_append {
371             my $self = shift;
372             my $config_hash = shift;
373              
374             for my $k ( keys %{ $config_hash } ) {
375             $self->api_config->{$k} = $config_hash->{$k};
376             }
377             }
378              
379             =head2 api_config_dump()
380              
381             This method is used for debugging
382              
383             =cut
384              
385             sub api_config_dump {
386             my $self = shift;
387              
388             my $str;
389            
390             while ( my( $key, $value ) = each( %{ $self->api_config } ) ) {
391             $str .= sprintf( "%s=%s\n", $key, $value );
392             }
393            
394             return $str;
395             }
396              
397             =head2 errors_append
398              
399             This method lets you append errors to the errors stack
400              
401             =cut
402              
403             sub errors_append {
404             my $self = shift;
405             my $hashref = shift;
406              
407             for my $k ( keys %{ $hashref } ) {
408             $self->errors->{$k} = $hashref->{$k};
409             }
410              
411             }
412              
413             =head1 PRIVATE METHODS
414              
415             =head2 _execute_http_request
416              
417             This method performs the http request and should be used by
418             each subclass.
419              
420             =cut
421              
422             sub _execute_http_request {
423             my $self = shift;
424              
425             # clear previous call data
426             $self->_reset();
427            
428             unless ( defined $self->{request_agent} ) {
429             $self->{request_agent} = $self->_get_request_agent();
430             }
431              
432             unless ( defined $self->{request_object} ) {
433             $self->{request_object} = $self->_get_request_object();
434             }
435              
436             if ( defined $self->api_config->{parallel} ) {
437             $self->{request_object}->{_ebay_api_simple_instance} = $self;
438             $self->api_config->{parallel}->register( $self->{request_object} );
439             return undef;
440             }
441              
442             my $max_tries = 1;
443            
444             if ( defined $self->api_config->{retry} ) {
445             $max_tries = $self->api_config->{retry} + 1;
446             }
447              
448             my $content = '';
449             my $error = '';
450             my $response;
451              
452             for ( my $i=0; $i < $max_tries; ++$i ) {
453             $response = $self->{request_agent}->request( $self->{request_object} );
454              
455             if ( $response->is_success ) {
456             last; # exit the loop
457             }
458             }
459              
460             $self->_process_http_request( $response );
461            
462             return $self->{response_content};
463             }
464              
465             =head2 _process_http_request
466              
467             This method processes the http request after it has completed.
468              
469             =cut
470              
471             sub _process_http_request {
472             my $self = shift;
473             my $response = shift;
474              
475             $self->{response_object} = $response;
476              
477             if ( $response->is_success ) {
478             my $content = $response->content();
479              
480             unless ($self->api_config->{preserve_namespace}) {
481             # strip out the namespace param, with single or double quotes
482             $content =~ s/xmlns=("[^"]+"|'[^']+') *//;
483             }
484              
485             $self->{response_content} = $content;
486              
487             # call the classes validate response method if it exists
488             $self->_validate_response() if $self->can('_validate_response');
489             }
490             else {
491             # store the error
492             my $error = $response->status_line;
493             $self->errors_append( { http_response => $error } ) if defined $error;
494              
495             my $content = $response->content();
496             $self->{response_content} = $content;
497             }
498             }
499              
500             =head2 _reset
501              
502             Upon execute() we need to undef any data from a previous call. This
503             method will clear all call data and is usually done before each execute
504              
505             =cut
506              
507             sub _reset {
508             my $self = shift;
509              
510             # clear previous call
511             $self->{errors} = undef;
512             $self->{response_object} = undef;
513             $self->{response_content} = undef;
514             $self->{request_agent} = undef;
515             $self->{request_object} = undef;
516             $self->{response_dom} = undef;
517             $self->{response_json} = undef;
518             $self->{response_hash} = undef;
519              
520             }
521              
522             =head2 _build_url( $base_url, $%params )
523              
524             Constructs a URL based on the supplied args
525              
526             =cut
527              
528             sub _build_url {
529             my $self = shift;
530             my $base = shift;
531             my $args = shift;
532              
533             my @p;
534             for my $k ( sort keys %{ $args } ) {
535             if ( ref( $args->{$k} ) eq 'ARRAY' ) {
536             for my $ap ( @{ $args->{$k} } ) {
537             push( @p,
538             ( $k . '=' . uri_escape_utf8( $ap ) )
539             );
540             }
541             }
542             else {
543             push( @p, ( $k . '=' . uri_escape_utf8( $args->{$k} ) ) );
544             }
545             }
546              
547             return( scalar( @p ) > 0 ? $base . '?' . join('&', @p) : $base );
548             }
549              
550             =head2 _get_request_body
551              
552             The request body should be provided by the subclass
553              
554             =cut
555              
556             sub _get_request_body {
557             my $self = shift;
558            
559             my $xml = "some content";
560              
561             return $xml;
562             }
563              
564             =head2 _get_request_headers
565              
566             The request headers should be provided by the subclass
567              
568             =cut
569              
570             sub _get_request_headers {
571             my $self = shift;
572            
573             my $obj = HTTP::Headers->new();
574              
575             $obj->push_header("SAMPLE-HEADER" => 'foo');
576            
577             return $obj;
578             }
579              
580             =head2 _get_request_agent
581              
582             The request request agent should be used by all subclasses
583              
584             =cut
585              
586             sub _get_request_agent {
587             my $self = shift;
588              
589             my $ua= LWP::UserAgent->new();
590              
591             $ua->agent( sprintf( '%s / eBay API Simple (Version: %s)',
592             $ua->agent,
593             $eBay::API::Simple::VERSION,
594             ) );
595              
596             # timeout in seconds
597             if ( defined $self->api_config->{timeout} ) {
598             $ua->timeout( $self->api_config->{timeout} );
599             }
600            
601             # add proxy
602             if ( $self->api_config->{http_proxy} ) {
603             $ua->proxy( ['http'], $self->api_config->{http_proxy} );
604             }
605              
606             if ( $self->api_config->{https_proxy} ) {
607             $ua->proxy( ['https'], $self->api_config->{https_proxy} );
608             }
609            
610             return $ua;
611             }
612              
613             =head2 _get_request_object
614              
615             The request object should be provided by the subclass
616              
617             =cut
618              
619             sub _get_request_object {
620             my $self = shift;
621              
622             my $url = sprintf( 'http%s://%s%s',
623             ( $self->api_config->{https} ? 's' : '' ),
624             $self->api_config->{domain},
625             $self->api_config->{uri}
626             );
627            
628             my $objRequest = HTTP::Request->new(
629             "POST",
630             $url,
631             $self->_get_request_headers,
632             $self->_get_request_body
633             );
634              
635             if( $self->api_config->{authorization_basic}{enabled} ) {
636             $objRequest->authorization_basic(
637             $self->api_config->{authorization_basic}{username},
638             $self->api_config->{authorization_basic}{password}
639             );
640             }
641            
642             return $objRequest;
643             }
644              
645             sub authorization_basic {
646             my $self = shift;
647             my $username = shift;
648             my $password = shift;
649             $self->api_config->{authorization_basic}{username} = $username;
650             $self->api_config->{authorization_basic}{password} = $password;
651             $self->api_config->{authorization_basic}{enabled} = 1;
652             }
653              
654             sub disable_authorization_basic {
655             my $self = shift;
656             $self->api_config->{authorization_basic}{enabled} = 0;
657             }
658              
659             =head2 _load_yaml_defaults
660              
661             This method will search for the ebay.yaml file and load configuration defaults
662             for each service endpoint
663              
664             YAML files can be placed at the below locations. The first file found will
665             be loaded.
666              
667             ./ebay.yaml, ~/ebay.yaml, /etc/ebay.yaml
668              
669             Sample YAML:
670              
671             # Trading - External
672             api.ebay.com:
673             appid:
674             certid:
675             devid:
676             token:
677            
678             # Shopping
679             open.api.ebay.com:
680             appid:
681             certid:
682             devid:
683             version: 671
684              
685             # Finding/Merchandising
686             svcs.ebay.com:
687             appid:
688             version: 1.0.0
689              
690              
691             =cut
692              
693             sub _load_yaml_defaults {
694             my $self = shift;
695              
696             return 1 if $self->{_yaml_loaded};
697            
698             my @files = (
699             "./ebay.yaml",
700             "/etc/ebay.yaml",
701             );
702              
703             push(@files, "$ENV{HOME}/ebay.yaml") if defined ($ENV{HOME});
704              
705             foreach my $file ( reverse @files ) {
706              
707             if ( open( FILE, "<", $file ) ) {
708              
709             my $yaml;
710             {
711             local $/ = undef;
712             $yaml = ;
713             }
714              
715             my $hashref = YAML::Load($yaml);
716             my $domain = $self->api_config->{domain};
717              
718             if ( defined $hashref->{ $domain } ) {
719             $self->api_config_append( $hashref->{ $domain } );
720             }
721            
722             $self->{_yaml_loaded} = 1;
723             close FILE;
724             last;
725             }
726             }
727              
728              
729             }
730              
731             1;
732              
733             =head1 AUTHOR
734              
735             Tim Keefer
736              
737             =head1 CONTRIBUTOR
738              
739             Andrew Dittes
740             Brian Gontowski
741              
742             =cut