#!/usr/bin/perl -s ########################################################################## # # Module: ............... /eBay/API/XML # File: ................. Session.pm # Original Author: ...... Bob Bradley # Last Modified By: ..... Jeff Nokes # Last Modified: ........ 03/30/2007 @ 18:33 # # This class is used to control api calls made in parallel, offer some # transactional logic when executing the calls, as well as retries. # ########################################################################## =head1 eBay::API::XML::Session Cluster and submit several eBay XML API calls at once. =head1 DESCRIPTION This module collects multiple requests to the eBay XML API and submits them sequentially or in parallel. Session.pm uses the CPAN module, LWP::Parallel, to manage the parallel submission of HTTP requests to the eBay XML API. =head1 SYNOPSIS use eBay::API::XML::Call::GeteBayOfficialTime; use eBay::API::XML::Call::GetUser; use eBay::API::XML::DataType::Enum::DetailLevelCodeType; use eBay::API::XML::Call::GetSearchResults; use eBay::API::XML::DataType::PaginationType; use eBay::API::XML::Session; # Create a session (authorization info is pulled from ENV by the constructors) my $session = new eBay::API::XML::Session; # Get official time. my $pCall = eBay::API::XML::Call::GeteBayOfficialTime->new(); $session->addRequest($pCall); # Get user details my $getUserCall = eBay::API::XML::Call::GetUser->new(); $getUserCall->setDetailLevel( [eBay::API::XML::DataType::Enum::DetailLevelCodeType::ReturnAll] ); $session->addRequest($getUserCall); # Get search results my $getListingsCall = new eBay::API::XML::Call::GetSearchResults; $getListingsCall->setQuery("new"); my $pagination = new eBay::API::XML::DataType::PaginationType; $pagination->setEntriesPerPage(10); $getListingsCall->setPagination($pagination); $session->addRequest($getListingsCall); # session will submit the calls in parallel -- then wait til all come back $session->execute(); # get results from various calls my $itemarray = $getListingsCall->getSearchResultItemArray()->getSearchResultItem(); my $officialtime = $pCall->getEBayOfficialTime(); my $pUser = $getUserCall->getUser(); my $sStatusCode = $pUser->getStatus(); my $sSiteCode = $pUser->getSite(); =cut # Package Declaration # ------------------------------------------------------------------------- package eBay::API::XML::Session; # Required Includes # ------------------------------------------------------------------------- use strict; use warnings; use Exporter; use LWP::Parallel; # http://search.cpan.org/~marclang/ParallelUserAgent-2.57/ # Support for submitting bundled requests in parallel. use Data::Dumper; use eBay::API::XML::BaseXml; # parent class use HTTP::Request; # Global Variables our $VERSION = '0.01'; # The version of this module. our @ISA = ( 'Exporter', 'eBay::API::XML::BaseXml', # Parent class with logging framework ); # :DEFAULT exported symbols our @EXPORT = qw( ); # Subroutine Prototypes # ---------------------------------------------------------------------------------- # Method Name Accessor Priviledges Method Type sub new($;$); sub addRequest($$;$); # Public Instance sub clearSession($;); # Public Instance sub execute($;); # Public Instance sub setSequentialExecution($$;); # Public Instance sub isSequentialExecution($;); # Public Instance sub _execute_sequential($;); # Private Instance sub _execute_parallel($;); # Private Instance sub _process_callback($$;); # Private Instance # Main Script # --------------------------------------------------------------------------- # # Subroutine Definitions # --------------------------------------------------------------------------- =head1 Subroutines: =pod =head2 new() Session constructor. This constructor delegates most of the work to the constructor for the abstract parent class, eBay::API::BaseApi. See perldoc eBay::API::BaseApi for more details. Arguments: =over 4 =item * B If this is true (non-zero), then the bundled calls will be executed sequentially. See method setSequentialExecution() for more details. =back Returns: =over 4 =item * B A reference to a blessed Session object. =item * B Undefined if an exception was encountered during construction of the session object. In that case, consult the log file for more details. =back =cut sub new($;$) { my $class = shift; my $arg_hash = shift; # validate that first arguments is blessed object eBay::API::BaseApi::_check_arg($class, Params::Validate::SCALAR); # more validations are done in the parent classes my $self = $class->SUPER::new($arg_hash); if (defined $self) { $self->clearSession(); if (defined($arg_hash)) { # validate that the arguements are a reference to a hash eBay::API::BaseApi::_check_arg($arg_hash, Params::Validate::HASHREF); if ($arg_hash->{sequential}) { $self->{sequential} = $arg_hash->{sequential}; } } } return $self; } =head2 addRequest() Instance method to add an eBay::API::XML::Call to the request bundle. Arguments: =over 4 =item * Object reference of type eBay::API::XML::Session =item * Reference to an eBay::API::XML::Call to be issued to the eBay XML API. =item * Optional reference to a callback subroutine to be called when the http request returns. Note. This subroutine will be called whether the return is a success, a failure, or a timeout. Argument going to the callback subroutine is the call object it is associated with. =back Returns: None =cut sub addRequest($$;$) { my ($self, $apicall, $callback) = @_; # validate that first two arguments are blessed objects eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); eBay::API::BaseApi::_check_arg($apicall, Params::Validate::OBJECT); unshift (@{$self->{requestqueue}}, $apicall); if (defined $callback) { eBay::API::BaseApi::_check_arg($callback, Params::Validate::CODEREF); $self->{callbacks}{$apicall} = $callback; } } =pod =head2 clearSession() Reset an eBay::API::XML::Session object so it may be re-used. This involves the following: =over 4 =item * Remove all bundled eBay::API::XML::Request objects. =item * Clear error information if present. =back Arguments: =over 4 =item * Object reference of type eBay::API::XML::Session =back Returns: =over 4 =item * B Object reference to the eBay::API::XML::Session. =item * B undefined =back =cut sub clearSession($;) { my $self = shift; # validate that first arguments is blessed object eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); # iniitialize Session member variables to sane values $self->_setError(''); $self->{requestqueue} = (); $self->{sequential} = 0; $self->{callbacks} = {}; return $self; } =head2 execute() Instance method used for executing the actual XML API request bundle. This method really does most of the work. It will attempt to perform all necessary validations, as well as create and send the bundle of XML requests. This method will block until all issued requests have responses, or until the timeout. After the responses come back from the API, they are populated back into the call objects registered with the session so they can be accessed from the client application. execute() also returns the eBay::API::XML::Response objects in an array. This array may have responses for all, some, or none of the issued requests, depending on the success or failure of each request. If the client application wants to use the request objects in the returned array, it should match up each response in the array with the corresponding request. This can best be done by using and tracking a unique message id for each request. If an incomplete set of responses are returned, an appropriate error will be set and available to the getError() method. Arguments: =over 4 =item * Object reference of type eBay::API::XML::Session =back Returns: =over 4 =item * B Reference to array of api call objects submittted to the eBay API. The calls may, or may not have executed successfully; it is up to the user to check error status of the session and possibly individual calls. =item * B undefined =back =cut sub execute($;) { # Get all values passed in. my $self = shift; # validate that first argument is blessed object eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); if ($self->isSequentialExecution()) { return $self->_execute_sequential(); } else { return $self->_execute_parallel(); } } # _execute_parallel() # Use LWP::Parallel to do parallel processing of the bundled calls. sub _execute_parallel($;) { my $self = shift; # validate that first argument is blessed object eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); # Set up for retries. # If the user has registered a retry object with retry requirements for the # session, then use those requirements. Otherwise, default to only 1 try (that # is to say, no retries. The retry object has two main things: 1) the number # of times to retry, and 2) the types of errors that qualify for retry. # See perldoc eBay::API::XML::CallRetry for more details on the retry object. my @calls = @{$self->{requestqueue}}; # egg basket; take calls out and put back in to retry my $moreretries = 1; my $retryobj = $self->getCallRetry(); if ( defined $retryobj ) { $moreretries = $retryobj->getMaximumRetries(); } # BEGIN RETRY LOOP # Loop through the current egg basket while ($moreretries && @calls) { $moreretries--; my %requests; my $parallel_agent = new LWP::Parallel::UserAgent; # Register the requests. In the case of retries, we # only register those requests that were unsuccessful # in the previous try. while (my $apicall = pop(@calls)) { my $httprequest = $apicall->_getHttpRequestObject(); $requests{$httprequest} = $apicall; $parallel_agent->register($httprequest); } # submit parallel and wait my $entries = $parallel_agent->wait($self->getTimeout()); # Process the responses. while ( my ($key, $entry) = each %$entries) { my $httpresponse = $entry->{response}; my $apicall = $requests{$key}; # The base call does most of the work here. $apicall->processResponse($httpresponse); # test for errors and possibly retry if ($moreretries && defined $retryobj && $retryobj->shouldRetry( 'raErrors' => $apicall->getErrorsAndWarnings() )) { unshift(@calls, $apicall); } } } # END RETRY LOOP # Process the callbacks and any errors foreach (@{$self->{requestqueue}}) { $self->_process_callback($_); if ($_->hasErrors()) { my ($error) = @{$_->getErrors()}; $self->_setError($error->getShortMessage); } } # Return the api call bundle return $self->{requestqueue}; } #_execute_sequential #Private instance method to execute the bundled calls in the #sequence in which they were added to the bundle. sub _execute_sequential($;) { my $self = shift; # validate that first arguments is blessed object eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); # Calls were unshifted on to the request queue array # in the order in which they were added to the session. # Now pop them off; and execute the calls in the same # sequence. my @calls = @{$self->{requestqueue}}; while (my $call = pop(@calls)) { $call->execute(); $self->_process_callback($call); if ($call->hasErrors()){ my ($error) = @{$call->getErrors()}; $self->_setError($error->getShortMessage); last; } } return $self->{requestqueue}; } # _process_callback() # Private method. Check if a call was registered with a callback. # If so, then call the callback. sub _process_callback($$;) { my $self = shift; my $call = shift; # validate that first two arguments are blessed objects eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); eBay::API::BaseApi::_check_arg($call, Params::Validate::OBJECT); # If this call has a callback associated with it, then call # the callback. if (exists($self->{callbacks}{$call})) { my $callback = $self->{callbacks}{$call}; &$callback($call); } } =head2 isSequentialExecution() Returns current state of the session with regard to whether session calls should be executed in sequence as an ordered transaction rather than in parallel. Arguments: =over 4 =item * Object reference of type eBay::API::XML::Session. =back Returns: =over 4 =item * B Session is set to issue calls in parallel. =item * B Session will issue calls sequentially. =back =cut sub isSequentialExecution($;) { my $self = shift; # validate that first argument is blessed object eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); return $self->{sequential}; } =head2 setSequentialExecution() Instance method to prepare the session to execute the API requests bundled in the session in the sequence in which they were added to the session. The only difference between this and the normal execution state is that the execute() method will execute each api call asynchronously rather than in parallel. If an error is encountered, none of the calls after the error was encountered will be sent to the eBay API. This behavior, in effect, offers a kind application-level transaction integrity, although there is no concept of 'rollback' in the sense of backing out the effects of calls executed prior to the error was encountered. See the description of the execute() subroutine for more details. Arguments: =over 4 =item * Object reference of type eBay::API::XML::Session. =item * Boolean. True will set execution mode to sequential. False will set execution mode to parallel. =back Returns: =over 4 =item * B Value set for execution mode. =item * B undefined =back =cut sub setSequentialExecution($$;) { my ($self, $bool) = @_; # validate that first argument is blessed object eBay::API::BaseApi::_check_arg($self, Params::Validate::OBJECT); $self->{sequential} = $bool; return $bool; } # Return TRUE to perl 1;