File Coverage

blib/lib/Net/Dynect/REST.pm
Criterion Covered Total %
statement 93 175 53.1
branch 31 90 34.4
condition 13 35 37.1
subroutine 19 22 86.3
pod 10 10 100.0
total 166 332 50.0


line stmt bran cond sub pod time code
1             package Net::Dynect::REST;
2             # $Id: REST.pm 175 2010-09-27 07:28:53Z james $
3 1     1   27710 use strict;
  1         2  
  1         37  
4 1     1   5 use warnings;
  1         2  
  1         26  
5 1     1   622 use Net::Dynect::REST::Request;
  1         3  
  1         28  
6 1     1   518 use Net::Dynect::REST::Response;
  1         3  
  1         1239  
7 1     1   645 use Net::Dynect::REST::Session;
  1         19  
  1         24  
8 1     1   8283 use LWP::UserAgent;
  1         100893  
  1         50  
9 1     1   2674 use HTTP::Request::Common;
  1         3580  
  1         296  
10 1     1   2446 use Time::HiRes qw(gettimeofday tv_interval);
  1         3715  
  1         7  
11 1     1   292 use Carp qw(carp cluck);
  1         2  
  1         2649  
12             our $VERSION = do { my @r = (q$Revision: 175 $ =~ /\d+/g); sprintf "%d."."%03d" x $#r, @r };
13              
14             =head1 NAME
15              
16             Net::Dynect::REST - A REST implementation to communicate with Dynect
17              
18             =head1 SYNOPSIS
19              
20             use Net::Dynect::REST
21             my $dynect = Net::Dynect::REST->new();
22             $dynect->login(user_name => $user, customer_name => $customer, password => $password;
23              
24             =head1 METHODS
25              
26             =head2 Creating
27              
28             =over 4
29              
30             =item Net::Dynect::REST->new()
31              
32             This constructor will return an object, and can optionally attempt to establish a session if sufficient authentication details are passed as parameters. It takes the optional arguments of:
33              
34             =over 4
35              
36             =item * debug
37              
38             A numeric debug level, where 0 is silent, 1 is standard output, and higher gives more details.
39              
40             =item * server
41              
42             =item * protocol
43              
44             =item * base_path
45              
46             =item * port
47              
48             =item * user_name
49              
50             =item * customer_name
51              
52             =item * password
53              
54             =over 4
55              
56             =back
57              
58             =back
59              
60             =cut
61              
62             sub new {
63 2     2 1 16 my $proto = shift;
64 2   33     17 my $self = bless {}, ref($proto) || $proto;
65 2         11 my %args = @_;
66 2 50       7 $self->_debug_level( $args{debug} ) if defined $args{debug};
67 2 100       13 $self->server( $args{server} ) if defined $args{server};
68 2 100       13 $self->protocol( $args{protocol} ) if defined $args{protocol};
69 2 100       10 $self->base_path( $args{base_path} ) if defined $args{base_path};
70 2 100       10 $self->port( $args{port} ) if defined $args{port};
71 2 0 33     9 if ( defined( $args{user_name} )
      33        
72             && defined( $args{password} )
73             && defined( $args{customer_name} ) )
74             {
75 0         0 my $login = $self->login(
76             user_name => $args{user_name},
77             customer_name => $args{customer_name},
78             password => $args{password}
79             );
80 0 0       0 if ( not $login ) {
81              
82             #carp "Tried to log in, but failed";
83 0         0 return;
84             }
85             }
86 2         8 return $self;
87             }
88              
89             =back
90              
91             =head2 Methods
92              
93             =over 4
94              
95             =item $dynect->login()
96              
97             This will attempt to create a valid Session object by forming and sending a login request, and parsing the response. Parameters are:
98              
99             =over 4
100              
101             =item * user_name
102              
103             =item * customer_name
104              
105             =item * password
106              
107             =back
108              
109             =cut
110              
111             sub login {
112 0     0 1 0 my $self = shift;
113 0         0 my %args = @_;
114 0 0 0     0 if (
      0        
115             not( defined( $args{user_name} )
116             && defined( $args{customer_name} )
117             && defined( $args{password} ) )
118             )
119             {
120 0         0 carp "Login method requires user_name, customer_name and password";
121 0         0 return;
122             }
123              
124 0         0 my $dynect_rest_request = Net::Dynect::REST::Request->new(
125             operation => 'create',
126             service => 'Session',
127             params => {
128             user_name => $args{user_name},
129             customer_name => $args{customer_name},
130             password => $args{password}
131             }
132             );
133              
134 0 0       0 if ( not $dynect_rest_request ) {
135 0         0 carp "Invalid request object";
136 0         0 return;
137             }
138              
139 0         0 my $dynect_rest_response = $self->execute($dynect_rest_request);
140              
141 0 0       0 if ( not $dynect_rest_response ) {
142 0         0 carp "Did not get a response object";
143 0         0 return;
144             }
145              
146 0 0       0 if ( $dynect_rest_response->status !~ /^success$/i ) {
147 0         0 carp join( ', ', map { $_->info } @{ $dynect_rest_response->msgs } );
  0         0  
  0         0  
148 0         0 return;
149             }
150              
151             $self->session(
152 0         0 Net::Dynect::REST::Session->new( response => $dynect_rest_response ) );
153 0         0 return 1;
154             }
155              
156             =item $dynect->logout()
157              
158             If we have a valid session, then this will try and perform a logout against Dynect, and remove our sesssion object.
159              
160             =cut
161              
162             sub logout {
163 0     0 1 0 my $self = shift;
164 0 0       0 if ( not $self->session ) {
165 0         0 carp "Cannot logout with out current session";
166 0         0 return;
167             }
168              
169 0         0 my $dynect_rest_request = Net::Dynect::REST::Request->new(
170             operation => 'delete',
171             service => 'Session'
172             );
173 0 0       0 if ( not $dynect_rest_request ) {
174 0         0 carp "Invalid request object";
175 0         0 return;
176             }
177              
178 0         0 my $dynect_rest_response = $self->execute($dynect_rest_request);
179              
180 0 0       0 if ( not $dynect_rest_response ) {
181 0         0 carp "Did not get a response";
182 0         0 return;
183             }
184              
185 0 0       0 if ( $dynect_rest_response->status !~ /^success$/i ) {
186 0         0 carp "Could not log out";
187 0         0 return;
188             }
189 0         0 $self->session(undef);
190 0         0 return 1;
191             }
192              
193             =item $dynect->execute()
194              
195             This is the main heavy lifting; where Net::Dynect::REST::Request objects get sent to the server, and a Net::Dynect::REST::Response is returned, if all is OK. It takes one argument - the Net::Dynect::REST::Request object.
196              
197             =cut
198              
199             sub execute {
200 1     1 1 12 my $self = shift;
201 1         3 my $dynect_rest_request = shift;
202 1 50 33     8 if ( ref($dynect_rest_request) ne "Net::Dynect::REST::Request" ) {
    50          
203 0         0 carp "Need a request to execute";
204 0         0 return;
205             }
206             elsif (
207             not( defined( $dynect_rest_request->service )
208             && defined( $dynect_rest_request->operation ) )
209             )
210             {
211 0         0 carp "Error with request - need to set operation and service: "
212             . $dynect_rest_request;
213 0         0 return;
214             }
215              
216 1         6 my $uri = $self->base_uri . $dynect_rest_request->service . "/";
217              
218 1         3 my $http_request;
219 1 50       5 if ( $dynect_rest_request->operation eq "create" ) {
    0          
    0          
    0          
220 1 50       4 if ( $dynect_rest_request->params ) {
221 0         0 $http_request = HTTP::Request::Common::POST(
222             $uri,
223             'Content-Type' => $dynect_rest_request->mime_type
224             . '; charset=utf-8',
225             Content => $dynect_rest_request->params
226             );
227             }
228             else {
229 1         4 $http_request = HTTP::Request::Common::POST( $uri,
230             'Content-Type' => $dynect_rest_request->mime_type
231             . '; charset=utf-8' );
232             }
233             }
234             elsif ( $dynect_rest_request->operation eq "read" ) {
235 0 0       0 if ( $dynect_rest_request->params ) {
236 0         0 $http_request = HTTP::Request::Common::GET(
237             $uri,
238             'Content-Type' => $dynect_rest_request->mime_type
239             . '; charset=utf-8',
240             Content => $dynect_rest_request->params
241             );
242             }
243             else {
244 0         0 $http_request = HTTP::Request::Common::GET( $uri,
245             'Content-Type' => $dynect_rest_request->mime_type
246             . '; charset=utf-8' );
247             }
248             }
249             elsif ( $dynect_rest_request->operation eq "update" ) {
250 0 0       0 if ( $dynect_rest_request->params ) {
251 0         0 $http_request = HTTP::Request::Common::PUT(
252             $uri,
253             'Content-Type' => $dynect_rest_request->mime_type
254             . '; charset=utf-8',
255             Content => $dynect_rest_request->params
256             );
257             }
258             else {
259 0         0 $http_request = HTTP::Request::Common::PUT( $uri,
260             'Content-Type' => $dynect_rest_request->mime_type
261             . '; charset=utf-8' );
262             }
263             }
264             elsif ( $dynect_rest_request->operation eq "delete" ) {
265 0 0       0 if ( $dynect_rest_request->params ) {
266 0         0 $http_request = HTTP::Request::Common::DELETE(
267             $uri,
268             'Content-Type' => $dynect_rest_request->mime_type
269             . '; charset=utf-8',
270             Content => $dynect_rest_request->params
271             );
272             }
273             else {
274 0         0 $http_request = HTTP::Request::Common::DELETE( $uri,
275             'Content-Type' => $dynect_rest_request->mime_type
276             . '; charset=utf-8' );
277             }
278             }
279             else {
280 0         0 die "Unrecognised operation: " . $dynect_rest_request->operation;
281             }
282              
283 1         12729 $self->_debug( 4, "Making a request to $uri" );
284 1         10 $self->_debug( 5,
285             "Request will be:\n" . $dynect_rest_request . "\n" . ( '-' x 70 ) );
286              
287 1         19 my $time_start = [gettimeofday];
288              
289 1         7 my $http_response = $self->_webclient->request($http_request);
290              
291 1 50       1414578 if ($http_response->code eq 307) {
292 0 0       0 if ($http_response->decoded_content =~ m!/REST/Job/(\d+)$!) {
293 0         0 my $job = Net::Dynect::REST::Job->new(connection => $self, job_id => $1);
294 0         0 carp "Request was deferred; a Job object is being returned. Please check back on ->find() shortly to get your response.";
295 0         0 return $job;
296             } else {
297 0         0 carp "We got a 307 but we couldnt understand it: ". $http_response->decoded_content;
298 0         0 return;
299             }
300             }
301              
302 1         20 my $time_elapsed = tv_interval($time_start);
303 1         29 my $dynect_rest_response = Net::Dynect::REST::Response->new(
304             content => $http_response->decoded_content,
305             format => $dynect_rest_request->format,
306             request_duration => $time_elapsed,
307             request_time => $time_start->[0]
308             );
309              
310 3         9 $self->_debug( 2,
311 1 50       7 join( '; ', map { $_->info } @{ $dynect_rest_response->msgs } ) ) if defined $dynect_rest_response->msgs;
  1         5  
312 1         13 $self->_debug( 5,
313             "Response received in $time_elapsed:\n"
314             . $dynect_rest_response . "\n"
315             . ( '-' x 70 ) );
316              
317 1         107 return $dynect_rest_response;
318             }
319              
320             =item $dynect->session()
321              
322             This is a Net::Dynect::REST::Session object, which should eb the current valid session for this Net::Dynect::REST object to use. It updates the web client to include the B header for subsequent requests
323              
324             =cut
325              
326             sub session {
327 0     0 1 0 my $self = shift;
328 0 0       0 if (@_) {
329 0         0 my $new = shift;
330 0 0 0     0 if ( defined($new) && ref($new) ne "Net::Dynect::REST::Session" ) {
331 0         0 carp "Invalid session: $new";
332 0         0 return;
333             }
334              
335 0         0 $self->{session} = $new;
336             return
337             unless
338 0 0       0 defined $new; # Allow the sesison to be undefined if its now dead!
339 0         0 $self->_debug( 6, "Adding auth token to default headers" );
340 0         0 $self->_webclient->default_headers(
341             HTTP::Headers->new( ':Auth-Token' => $new->token ) );
342             }
343 0         0 return $self->{session};
344             }
345              
346             sub _webclient {
347 1     1   2 my $self = shift;
348 1 50       6 if ( not defined $self->{_webclient} ) {
349 1         17 $self->{_webclient} = LWP::UserAgent->new(
350             agent => ref($self) . "/" . $VERSION,
351             env_proxy => 1
352             );
353             }
354 1         1215105 return $self->{_webclient};
355             }
356              
357             sub _debug_level {
358 4     4   5 my $self = shift;
359 4 50       15 if (@_) {
360 0         0 my $new = shift;
361 0 0       0 if ( $new !~ /^\d$/ ) {
362 0         0 carp "Invalid debug level: " . $new;
363 0         0 return;
364             }
365 0         0 $self->{_debug_level} = $new;
366 0         0 $self->_debug( 0, "Debug set to " . $self->{_debug_level} );
367             }
368 4   50     32 return $self->{_debug_level} || 0;
369             }
370              
371             sub _debug {
372 4     4   11 my $self = shift;
373 4         6 my ( $level, $message ) = @_;
374 4 50       17 return unless $level <= $self->_debug_level;
375              
376 0 0       0 if ( $level > 8 ) {
377 0         0 $Carp::CarpLevel = 1;
378 0         0 cluck $message;
379             }
380             else {
381 0         0 carp $message;
382             }
383             }
384              
385             =back
386              
387             =head2 Attributes
388              
389             =over 4
390              
391             =item $dynect->server()
392              
393             This is the server host name that we will send our requests to. Default is B.
394              
395             =cut
396              
397             sub server {
398 4     4 1 756 my $self = shift;
399 4 100       12 if (@_) {
400 1         2 my $new = shift;
401 1         8 $self->{server} = $new;
402             }
403 4   100     34 return $self->{server} || 'api2.dynect.net';
404             }
405              
406             =item $dynect->protocol()
407              
408             This is the protocol we will use, either B or B. Default is B.
409              
410             =cut
411              
412             sub protocol {
413 4     4 1 8 my $self = shift;
414 4 100       13 if (@_) {
415 1         2 my $new = shift;
416 1 50       10 return unless $new =~ /^https?$/;
417 1         4 $self->{protocol} = $new;
418             }
419 4   100     30 return $self->{protocol} || "https";
420             }
421              
422             =item $dynect->base_path()
423              
424             This is the path that is used to find the services we will be accessing. Default is B.
425              
426             =cut
427              
428             sub base_path {
429 4     4 1 6 my $self = shift;
430 4 100       12 if (@_) {
431 1         1 my $new = shift;
432 1 50       7 return unless $new =~ m!^/[\w\d/-]*$!;
433 1         3 $self->{base_path} = $new;
434             }
435 4   100     28 return $self->{base_path} || '/REST/';
436             }
437              
438             =item $dynect->port()
439              
440             The TCP port that we will use. The default is to use whatever is apropriate for the protocol.
441              
442             =cut
443              
444             sub port {
445 3     3 1 6 my $self = shift;
446 3 100       8 if (@_) {
447 1         2 my $new = shift;
448 1 50       8 return unless $new =~ /^\d+$/;
449 1         3 $self->{port} = $new;
450             }
451 3         14 return $self->{port};
452             }
453              
454             =item $dynect->base_uri()
455              
456             A convenience method to put together the protocl, server, port and base_path attributes into a URI.
457              
458             =cut
459              
460             sub base_uri {
461 1     1 1 3 my $self = shift;
462 1 50 33     5 return unless ( $self->protocol && $self->server && $self->base_path );
      33        
463             return
464 1 50       4 $self->protocol . "://"
465             . $self->server
466             . ( defined( $self->port ) ? ":" . $self->port : "" )
467             . $self->base_path;
468             }
469              
470             =back
471              
472             =head1 SEE ALSO
473              
474             L, L, L.
475              
476             =head1 AUTHOR
477              
478             James bromberger, james@rcpt.to
479              
480             =head1 COPYRIGHT AND LICENSE
481              
482             Copyright (C) 2010 by James Bromberger
483              
484             This library is free software; you can redistribute it and/or modify
485             it under the same terms as Perl itself, either Perl version 5.10.1 or,
486             at your option, any later version of Perl 5 you may have available.
487              
488             =cut
489              
490             1;