File Coverage

blib/lib/XML/OverHTTP.pm
Criterion Covered Total %
statement 91 162 56.1
branch 20 58 34.4
condition 2 6 33.3
subroutine 27 40 67.5
pod 10 15 66.6
total 150 281 53.3


line stmt bran cond sub pod time code
1             package XML::OverHTTP;
2 8     8   14972 use strict;
  8         20  
  8         302  
3 8     8   48 use vars qw( $VERSION @ISA );
  8         14  
  8         630  
4             $VERSION = '0.08';
5 8     8   20885 use XML::TreePP;
  8         128496  
  8         336  
6 8     8   19728 use CGI;
  8         136400  
  8         62  
7             # use Data::Page;
8             # use Data::Pageset;
9 8     8   550 use base qw( Class::Accessor::Fast );
  8         17  
  8         9358  
10             __PACKAGE__->mk_accessors(qw( xml tree code param ));
11              
12             if ( $XML::TreePP::VERSION < 0.26 ) {
13             Carp::croak( 'XML::TreePP version 0.26 or later is required' );
14             }
15              
16             package XML::OverHTTP::Default;
17 8     8   40884 use strict;
  8         20  
  8         239  
18 8     8   48 use vars qw( $VERSION );
  8         15  
  8         2270  
19             $VERSION = $XML::OverHTTP::VERSION;
20              
21 1     1   14 sub http_method { 'GET'; }
22 0     0   0 sub url { undef; }
23 7     7   18 sub query_class { undef; }
24 5     5   16 sub default_param { {}; }
25 0     0   0 sub notnull_param { []; }
26 1     1   7 sub force_array { []; }
27 0     0   0 sub force_hash { []; }
28 1     1   7 sub attr_prefix { ''; }
29 0     0   0 sub text_node_key { '#text'; }
30 1     1   5 sub elem_class { undef; }
31 0     0   0 sub root_elem { undef; }
32 1     1   8 sub is_error { undef; }
33 1     1   5 sub total_entries { undef; }
34 1     1   5 sub entries_per_page { undef; }
35 1     1   5 sub current_page { undef; }
36 0     0   0 sub page_param { undef; }
37              
38             package XML::OverHTTP; # again
39 8     8   46 use strict;
  8         13  
  8         266  
40 8     8   41 use base qw( XML::OverHTTP::Default );
  8         20  
  8         18276  
41              
42             sub new {
43 8     8 1 19541 my $package = shift;
44 8         21 my $self = {};
45 8         22 bless $self, $package;
46 8         55 my $default = $self->default_param();
47 8 50       91 $self->add_param( %$default ) if ref $default;
48 8 100       71 $self->add_param( @_ ) if scalar @_;
49 8         33 $self;
50             }
51              
52             sub new_param {
53 8     8 0 190 my $self = shift;
54 8         64 my $class = $self->query_class();
55 8 100       56 return {} unless defined $class;
56 2         20 $class->new();
57             }
58              
59             sub add_param {
60 12     12 1 28 my $self = shift;
61 12   66     54 my $param = $self->param() || $self->new_param();
62 12 100       155 %$param = ( %$param, @_ ) if scalar @_;
63 12         45 $self->param( $param );
64             }
65              
66             sub get_param {
67 4     4 1 14 my $self = shift;
68 4         5 my $key = shift;
69 4 50       9 my $param = $self->param() or return;
70 4 50       46 $param->{$key} if exists $param->{$key};
71             }
72              
73             sub treepp {
74 3     3 1 8918 my $self = shift;
75 3 50       10 $self->{treepp} = shift if scalar @_;
76 3 100       19 return $self->{treepp} if ref $self->{treepp};
77 1         11 $self->{treepp} = XML::TreePP->new();
78             }
79              
80             sub init_treepp {
81 0     0 0 0 my $self = shift;
82 0         0 my $treepp = $self->treepp();
83              
84 0         0 my $force_array = $self->force_array();
85 0         0 my $force_hash = $self->force_hash();
86 0         0 my $attr_prefix = $self->attr_prefix();
87 0         0 my $text_node_key = $self->text_node_key();
88             # my $base_class = $self->base_class();
89 0         0 my $elem_class = $self->elem_class();
90 0         0 $treepp->set( force_array => $force_array );
91 0         0 $treepp->set( force_hash => $force_hash );
92 0         0 $treepp->set( attr_prefix => $attr_prefix );
93 0         0 $treepp->set( text_node_key => $text_node_key );
94             # $treepp->set( base_class => $base_class );
95 0         0 $treepp->set( elem_class => $elem_class );
96              
97 0         0 $treepp;
98             }
99              
100             sub request {
101 0     0 1 0 my $self = shift;
102 0         0 $self->{tree} = undef;
103 0         0 $self->{xml} = undef;
104 0         0 $self->{code} = undef;
105 0         0 $self->{page} = undef;
106 0         0 $self->{pageset} = undef;
107              
108 0         0 $self->check_param();
109 0         0 my $req = $self->http_request();
110 0         0 my $treepp = $self->init_treepp();
111 0         0 my( $tree, $xml, $code ) = $treepp->parsehttp( @$req );
112              
113 0         0 $self->{tree} = $tree;
114 0         0 $self->{xml} = $xml;
115 0         0 $self->{code} = $code;
116 0         0 $tree;
117             }
118              
119             sub http_request {
120 0     0 0 0 my $self = shift;
121              
122 0         0 my $method = $self->http_method();
123 0         0 my $url = $self->url();
124 0         0 my $query = $self->query_string();
125 0 0       0 Carp::croak( 'HTTP method is not defined' ) unless defined $method;
126 0 0       0 Carp::croak( 'Request url is not defined' ) unless defined $url;
127              
128 0         0 my $req;
129 0 0       0 if ( uc($method) eq 'GET' ) {
130 0 0       0 $url .= '?'.$query if length($query);
131 0         0 $req = [ $method, $url ];
132             }
133             else {
134 0         0 $req = [ $method, $url, $query ];
135             }
136 0         0 $req;
137             }
138              
139             sub root {
140 0     0 1 0 my $self = shift;
141 0         0 my $tree = $self->tree();
142 0 0       0 Carp::croak( 'Empty response' ) unless ref $tree;
143 0         0 my $root = $self->root_elem();
144 0 0       0 Carp::croak( 'Root element is not defined' ) unless defined $root;
145 0 0       0 Carp::croak( 'Root element seems empty' ) unless ref $tree->{$root};
146 0         0 $tree->{$root};
147             }
148              
149             sub root_elem {
150 0     0 1 0 my $self = shift;
151 0         0 my $tree = $self->tree();
152 0 0       0 Carp::croak( 'Empty response' ) unless ref $tree;
153 0 0       0 Carp::croak( 'Multiple root elements found' ) if ( scalar keys %$tree > 1 );
154             # root element auto discovery by default
155 0         0 ( keys %$tree )[0];
156             }
157              
158             sub query_string {
159 2     2 0 14517 my $self = shift;
160 2 50       13 my $param = $self->param() or return;
161 2         37 local $CGI::USE_PARAM_SEMICOLONS = 0;
162 2         13 my $hash = { %$param }; # copy for blessed hash
163 2         18 CGI->new( $hash )->query_string();
164             }
165              
166             sub check_param {
167 0     0 0 0 my $self = shift;
168 0 0       0 my $param = $self->param() or return;
169 0 0       0 my $check = $self->notnull_param() or return;
170 0 0 0     0 my $error = [ grep {
171 0         0 ! exists $param->{$_} ||
172             ! defined $param->{$_} ||
173             $param->{$_} eq ''
174             } @$check ];
175 0 0       0 return unless scalar @$error;
176 0         0 my $join = join( ' ' => @$error );
177 0         0 Carp::croak "Invalid request: empty parameters - $join\n";
178             }
179              
180             sub page {
181 1     1 1 399 my $self = shift;
182 1         3 my $page = shift;
183 1 50       5 if ( ! defined $page ) {
184 1 50       3 return $self->{page} if ref $self->{page};
185 1         2 local $@;
186 1 50       4 eval { require Data::Page; } unless $Data::Page::VERSION;
  0         0  
187 1 50       3 Carp::croak( "Data::Page is required: $@" ) unless $Data::Page::VERSION;
188 1         5 $page = Data::Page->new();
189             }
190 1         47 my $total_entries = $self->total_entries();
191 1         8 my $entries_per_page = $self->entries_per_page();
192 1         5 my $current_page = $self->current_page();
193 1         23 $page->total_entries( $total_entries );
194 1         11 $page->entries_per_page( $entries_per_page );
195 1         12 $page->current_page( $current_page );
196 1         10 $self->{page} = $page;
197             }
198              
199             sub pageset {
200 1     1 1 473 my $self = shift;
201 1         2 my $mode = shift; # default 'fixed', or 'slide'
202 1 50       18 return $self->{pageset} if ref $self->{pageset};
203 1         8 my $total_entries = $self->total_entries();
204 1         7 my $entries_per_page = $self->entries_per_page();
205 1         7 my $current_page = $self->current_page();
206 1         7 my $hash = {
207             total_entries => $total_entries,
208             entries_per_page => $entries_per_page,
209             current_page => $current_page,
210             mode => $mode,
211             };
212 1         2 local $@;
213 1 50       3 eval { require Data::Pageset; } unless $Data::Pageset::VERSION;
  0         0  
214 1 50       2 Carp::croak( "Data::Pageset is required: $@" ) unless $Data::Pageset::VERSION;
215 1         7 $self->{pageset} = Data::Pageset->new( $hash );
216             }
217              
218             sub page_query {
219 0     0 1   my $self = shift;
220 0           my $param = $self->page_param( @_ );
221 0           local $CGI::USE_PARAM_SEMICOLONS = 0;
222 0           CGI->new( $param )->query_string();
223             }
224              
225             =head1 NAME
226              
227             XML::OverHTTP - A base class for XML over HTTP-styled web service interface
228              
229             =head1 DESCRIPTION
230              
231             This module is not used directly from end-users.
232             As a child class of this, module authors can easily write own interface module
233             for XML over HTTP-styled web service.
234              
235             =head1 METHODS PROVIDED
236              
237             This module provides some methods and requires other methods overridden by child classes.
238             The following methods are to be called in your module or by its users.
239              
240             =head2 new
241              
242             This constructor method returns a new object for your users.
243             It accepts query parameters by hash.
244              
245             my $api = MyAPI->new( %param );
246              
247             MyAPI.pm inherits this XML::OverHTTP modules.
248              
249             =head2 add_param
250              
251             This method adds query parameters for the request.
252              
253             $api->add_param( %param );
254              
255             It does not validate key names.
256              
257             =head2 get_param
258              
259             This method returns a current query parameter.
260              
261             $api->get_param( 'key' );
262              
263             =head2 treepp
264              
265             This method returns an L object to make the request.
266              
267             $api->treepp->get( 'key' );
268              
269             And you can set its object as well.
270              
271             my $mytpp = XML::TreePP->new;
272             $api->treepp( $mytpp );
273              
274             total_entries, entries_per_page and current_page parameters
275             in C<$mytpp> are updated.
276              
277             =head2 request
278              
279             This method makes the request for the web service and returns its response tree.
280              
281             my $tree = $api->request;
282              
283             After calling this method, the following methods are available.
284              
285             =head2 tree
286              
287             This method returns the whole of the response parsed by L parser.
288              
289             my $tree = $api->tree;
290              
291             Every element is blessed when L is defined.
292              
293             =head2 root
294              
295             This method returns the root element in the response.
296              
297             my $root = $api->root;
298              
299             =head2 xml
300              
301             This method returns the response context itself.
302              
303             print $api->xml, "\n";
304              
305             =head2 code
306              
307             This method returns the response status code.
308              
309             my $code = $api->code; # usually "200" when succeeded
310              
311             =head2 page
312              
313             This method returns a L object to create page navigation.
314              
315             my $pager = $api->page;
316             print "Last page: ", $pager->last_page, "\n";
317              
318             And you can set its object as well.
319              
320             my $pager = Data::Page->new;
321             $api->page( $pager );
322              
323             =head2 pageset
324              
325             This method returns a L object to create page navigation.
326             The paging mode is C as default.
327              
328             my $pager = $api->pageset;
329             $pager->pages_per_set( 10 );
330             print "First page of next page set: ", $page_info->next_set, "\n";
331              
332             Or set it to C mode if you want.
333              
334             my $pager = $api->pageset( 'slide' );
335              
336             =head2 page_param
337              
338             This method returns pair(s) of query key and value to set the page number
339             for the next request.
340              
341             my $hash = $api->page_param( $page );
342              
343             The optional second argument specifies the number of entries per page.
344              
345             my $hash = $api->page_param( $page, $size );
346              
347             The optional third argument incluedes some other query parameters.
348              
349             my $newhash = $api->page_param( $page, $size, $oldhash );
350              
351             =head2 page_query
352              
353             This method returns a processed query string which is joined by '&' delimiter.
354              
355             my $query = $api->page_query(); # current page
356             my $query = $api->page_query( $page, $size, $hash ); # specified page
357              
358             =head1 METHOD YOU MUST OVERRIDE
359              
360             You B override at least one method below:
361              
362             =head2 url
363              
364             This is a method to specify the url for the request to the web service.
365             E.g.,
366              
367             sub url { 'http://www.example.com/api/V1/' }
368              
369             =head1 METHODS YOU SHOULD OVERRIDE
370              
371             The methods that you B override in your module are below:
372              
373             =head2 root_elem
374              
375             This is a method to specify a root element name in the response.
376             E.g.,
377              
378             sub root_elem { 'rdf:RDF' }
379              
380             =head2 is_error
381              
382             This is a method to return C value when the response seems
383             to have error. This returns C when it succeeds.
384             E.g.,
385              
386             sub is_error { $_[0]->root->{status} != 'OK' }
387              
388             =head2 total_entries
389              
390             This is a method to return the number of total entries for C.
391             E.g.,
392              
393             sub total_entries { $_[0]->root->{hits} }
394              
395             =head2 entries_per_page
396              
397             This is a method to return the number of entries per page for C.
398             E.g.,
399              
400             sub entries_per_page { $_[0]->root->{-count} }
401              
402             =head2 current_page
403              
404             This is a method to return the current page number for C.
405             E.g.,
406              
407             sub current_page { $_[0]->root->{-page} }
408              
409             =head2 page_param
410              
411             This is a method to return paging parameters for the next request.
412             E.g.,
413              
414             sub page_param {
415             my $self = shift;
416             my $page = shift || $self->current_page();
417             my $size = shift || $self->entries_per_page();
418             my $hash = shift || {};
419             $hash->{page} = $page if defined $page;
420             $hash->{count} = $size if defined $size;
421             $hash;
422             }
423              
424             When your API uses SQL-like query parameters, offset and limit:
425              
426             sub page_param {
427             my $self = shift;
428             my $page = shift || $self->current_page() or return;
429             my $size = shift || $self->entries_per_page() or return;
430             my $hash = shift || {};
431             $hash->{offset} = ($page-1) * $size;
432             $hash->{limit} = $size;
433             $hash;
434             }
435              
436             =head1 METHODS YOU CAN OVERRIDE
437              
438             You B override the following methods as well.
439              
440             =head2 http_method
441              
442             This is a method to specify the HTTP method, 'GET' or 'POST', for the request.
443             This returns 'GET' as default.
444             E.g.,
445              
446             sub http_method { 'GET' }
447              
448             =head2 default_param
449              
450             This is a method to specify pairs of default query parameter and its value
451             for the request.
452             E.g.,
453              
454             sub default_param { { method => 'search', lang => 'perl' } }
455              
456             =head2 notnull_param
457              
458             This is a method to specify a list of query parameters which are required
459             by the web service.
460             E.g.,
461              
462             sub notnull_param { [qw( api_key secret query )] }
463              
464             These keys are checked before makeing a request for safe.
465              
466             =head2 query_class
467              
468             This is a method to specify a class name for query parameters.
469             E.g.,
470              
471             sub elem_class { 'MyAPI::Query' }
472              
473             The default value is C, it means
474             a normal hash is used instead.
475              
476             =head2 attr_prefix
477              
478             This is a method to specify a prefix for each attribute
479             in the response tree. L uses it.
480             E.g.,
481              
482             sub attr_prefix { '' }
483              
484             The default prefix is zero-length string C<""> which is recommended.
485              
486             =head2 text_node_key
487              
488             This is a method to specify a hash key for text nodes
489             in the response tree. L uses it.
490             E.g.,
491              
492             sub text_node_key { '_text' }
493              
494             The default key is C<"#text">.
495              
496             =head2 elem_class
497              
498             This is a method to specify a base class name for each element
499             in the response tree. L uses it.
500             E.g.,
501              
502             sub elem_class { 'MyAPI::Element' }
503              
504             The default value is C, it means
505             each elements is a just hashref and not bless-ed.
506              
507             =head2 force_array
508              
509             This is a method to specify a list of element names which should always
510             be forced into an array representation in the response tree.
511             L uses it.
512             E.g.,
513              
514             sub force_array { [qw( rdf:li item xmlns )] }
515              
516             =head2 force_hash
517              
518             This is a method to specify a list of element names which should always
519             be forced into an hash representation in the response tree.
520             L uses it.
521             E.g.,
522              
523             sub force_hash { [qw( item image )] }
524              
525             =head1 SEE ALSO
526              
527             L
528              
529             L
530              
531             =head1 AUTHOR
532              
533             Yusuke Kawasaki L
534              
535             =head1 COPYRIGHT AND LICENSE
536              
537             Copyright (c) 2007 Yusuke Kawasaki. All rights reserved.
538             This program is free software; you can redistribute it and/or
539             modify it under the same terms as Perl itself.
540              
541             =cut
542             1;