The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use perfSONAR_PS::DB::SQL::PingER;

package perfSONAR_PS::Datatypes::PingER;
{
=head1 NAME

 perfSONAR_PS::Datatypes::PingER  -  this is a pinger message  handler object


=head1 DESCRIPTION

   it inherits everything from perfSONAR_PS::Datatypes::Message and only implements  request handlers with pinger specifics 
    
=head1 SYNOPSIS
             
  
	     use perfSONAR_PS::Datatypes::PingER ;
	   
	     
	     my ($DOM) = $requestMessage->getElementsByTag('message');
	    
	     my $message = new perfSONAR_PS::Datatypes::PingER($DOM);
             $message = new perfSONAR_PS::Datatypes::PingER({id => '2345', 
	                                                     type = 'SetupdataResponse',
							     metadata => {'id1' =>   <obj>},
							     data=> {'id1' => <obj>}}); 
	 
	    #######   add data element, namespaces will be added from this object to Message object namespace declaration
             $message->addPartById('id1', 'data', new perfSONAR_PS::Datatypes::PingER::data({id=> 'id1', metadataIdRef => 'metaid1' }));
        
	    ########add metadata element, namespaces will be added from this object to Message object namespace declaration
	     $message->addPartById('id1', 'metadata',  new perfSONAR_PS::Datatypes::PingER::metadata({id=> 'id1' });
	     
	     my $dom = $message->getDOM(); # get as DOM 
	     print $message->asString();  # print the whole message
	     
	     
=head1   METHODS

=cut


use strict;
use warnings;
use English qw( -no_match_vars);
use Log::Log4perl qw(get_logger); 
use POSIX qw(strftime);
use Data::Dumper;
use Scalar::Util qw(blessed);
use perfSONAR_PS::Datatypes::Namespace;
use perfSONAR_PS::Datatypes::v2_0::nmwg::Message; 
use perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data;
use perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata;
use perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Key;
use perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data::Key;
use perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data::CommonTime;

use perfSONAR_PS::Datatypes::v2_0::nmwgr::Message::Data::Datum;

use perfSONAR_PS::Datatypes::v2_0::pinger::Message::Parameters;
use perfSONAR_PS::Datatypes::v2_0::pinger::Message::Metadata::Parameters;

use perfSONAR_PS::Datatypes::v2_0::pinger::Message::Metadata::Subject; 
use perfSONAR_PS::Datatypes::v2_0::pinger::Message::Data::CommonTime::Datum;

use perfSONAR_PS::Datatypes::v2_0::select::Message::Metadata::Parameters;
use perfSONAR_PS::Datatypes::v2_0::select::Message::Metadata::Subject;

use perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Parameters::Parameter;
use perfSONAR_PS::Datatypes::v2_0::nmwgt::Message::Metadata::Subject::EndPointPair::Dst;
use perfSONAR_PS::Datatypes::v2_0::nmwgt::Message::Metadata::Subject::EndPointPair::Src;
use perfSONAR_PS::Datatypes::v2_0::nmwgt::Message::Metadata::Subject::EndPointPair;

use perfSONAR_PS::Datatypes::NSMap;
use perfSONAR_PS::Datatypes::EventTypes;
 
 
use base qw(perfSONAR_PS::Datatypes::Message);
 
use constant  CLASSPATH =>  'perfSONAR_PS::Datatypes::PingER';
use constant  LOCALNAME => 'message'; 

#
#   internal limit for the number of the data/metadata in the response message
#
our $_sizeLimit = '1000000'; 

 
 
=head2 handle 

   dispatch method. accepts type of request,response object and MA config hashref as parameters, returns fully built response object,
   sets query size limit  

=cut

sub handle {
    my $self = shift;
    my $type = shift;
    my $response = shift; 
    my $maconfig = shift;
    my $logger  = get_logger( CLASSPATH );
    no strict 'refs';  
    if($self->can($type)) {
        $_sizeLimit = $maconfig->{query_size_limit} if $maconfig && ref($maconfig) eq 'HASH' && $maconfig->{query_size_limit};
        $logger->debug(" Size limit set to:  $_sizeLimit ");       
        return $type->($self, $response);
    } else {
       $logger->error(" Handler for $type Not supported");
       return undef;
    }
    use strict;
}


=head2   MetadataKeyRequest

      method for MetadataKey request,  works per event ( single pre-merged md  and data pair)
      returns filled response message object 

  ###############################  From Jason's SNMP MA code ################################### 
  # MA MetadataKeyRequest Steps
  # ---------------------
  # Is there a key?
  #   Y: do queries against key 
  #      return key as md AND d
  #   N: Is there a chain?
  #     Y: Is there a key
  #       Y: do queries against key, add in select stuff to key, return key as md AND d
  #       N: do md/d queries against md return results with altered key
  #     N: do md/d queries against md return results
  #--------------------------
 
=cut

sub  MetadataKeyRequest {
    my $self = shift;
    my $response = shift;
    my $logger  = get_logger( CLASSPATH );   
    $logger->debug("MetadataKeyRequest  ...");
 
    unless($response && blessed  $response &&   $response->can("getDOM")) {
        $logger->error(" Please supply  metadata  object and not the:" . ref($response));
        return  " System error, API incomplete";
    }
    ##my $message_params = perfSONAR_PS::Datatypes::v2_0::select::Message::Parameters->new();
    ##my $message_limit = perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Parameters::Parameter->new({name => 'sizeLimit', value =>  $_sizeLimit});
    ##$message_params->addParameter($message_limit);
    ##$response->addParameters($message_params);
    #### setting status URI for response 
    $self->eventTypes->status->operation('metadatakey');
    $logger->error(" Please supply  array of metadata and not: " . $self->metadata ) unless $self->metadata && ref($self->metadata) eq 'ARRAY';
    my $data  =   $self->data->[0];
    my $requestmd =  $self->metadata->[0];
    my  $objects  = undef;### iterator returned by backend query
    if($requestmd->key  &&  $requestmd->key->id) {
        $logger->debug(" Found Key =" . $requestmd->key->id);	  
        $objects  = $self->DBO->getMeta([ metaID=> {eq =>  $requestmd->key->id}], 1);
	     
    }  elsif(($requestmd->subject) ||  ($requestmd->parameters)) { 
        my  $query = {query_metaData => []};
        $query =  $self->buildQuery('eq', $requestmd );
	$query =  {query_metaData => []} unless $query;
	$logger->debug(" Will query = " . Dumper $query);	 	
	$objects  =   $self->DBO->getMeta(  $query->{query_metaData},  _mdSetLimit($query->{query_limit}) );
	 
	if($EVAL_ERROR) {
	    $logger->logdie("PingER backend  Query failed: " . $EVAL_ERROR);
	}
    }  else {
	$logger->warn("Malformed request or missing key or parameters, md=" . $requestmd->id);   
	$response->addResultResponse({ md => $requestmd, message => 'Malformed request or missing key or parameters',  eventType => $self->eventTypes->status->failure});	
        return;     
    }
     
    if($objects &&   ref($objects) eq 'HASH' &&  %{$objects}) {
	foreach my  $metaid (keys %{$objects}) {
	    $logger->debug( "Found metaID " .  $metaid  );
	    my $md =   $self->_ressurectMd({ md_row =>  $objects->{$metaid}  });  
	    my $md_id = $response->addIDMetadata($md, $self->eventTypes->tools->pinger);
            $logger->debug(" MD created: \n " .  $md->asString);
	    my $key =   perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data::Key->new({id => $metaid });
	    $logger->debug(" KEY  created: \n " .  $key->asString );
	    my $data  = perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data->new({ id => "data" . $response->dataID, metadataIdRef => "meta$md_id" , key => $key  });
	    $logger->debug(" DATA created: \n " .  $data->asString);
	    $response->addData($data); ## 
	    $logger->debug(" DATA added");
	    $response->add_dataID;
	}    
    } else {           
        $response->addResultResponse({ md =>  $requestmd, message => ' no metadata found ', eventType => $self->eventTypes->status->failure});
    }
    
    return  0;
}


=head2   SetupDataRequest

   SetupData request,    works per event ( single pre-merged md  and data pair)
      returns filled response message object 
      returns filled response  
  ###############################  From Jason's SNMP MA code ################################### 
  # MA SetupdataRequest Steps
  # ---------------------
  # Is there a key?
  #   Y: key in db?
  #     Y: Original MD in db?
  #       Y: add it, extract data
  #       N: use sent md, extract data
  #     N: error out
  #   N: Is it a select?
  #     Y: Is there a matching MD?
  #       Y: Is there a key in the matching md?
  #         Y: Original MD in db?
  #           Y: add it, extract data
  #           N: use sent md, extract data
  #         N: extract md (for correct md), extract data
  #       N: Error out
  #     N: extract md, extract data
  #----------------------------------
  #     Updated from Jason e-mail, 11/27/07
  #
  #   Is there a key?
  #     Y: key in db?
  #         Y: chain metadata      
  #            extract data
  #         N: error out
  #     N: Is it a select?
  #         Y: Is there a matching MD?
  #             Y: Is there a key in the matching md?
  #                  Y: key in db?
  #                      Y: chain metadata 
  #                         extract data
  #                      N: error out
  #                 N:  chain metadata
  #                     extract data     
  #             N: Error out
  #        N: chain metadata
  #           extract data 
  #
  # 
  
=cut

sub  SetupDataRequest  {
    my $self = shift;
    my $response = shift;
    
    my $logger  = get_logger( CLASSPATH ); 
    $logger->debug("SetupdataKeyRequest  ..."); 
 
    unless($response && blessed  $response &&   $response->can("getDOM")) {
        $logger->error(" Please supply defined object of metadata and not:" . ref($response));
        return "System error,  API incomplete";
    }
    ##my $message_params = perfSONAR_PS::Datatypes::v2_0::select::Message::Metadata::Parameters->new();
    ##my $message_limit  = perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Parameters::Parameter->new({name => 'sizeLimit', value =>  $_sizeLimit});
    ##$message_params->addParameter($message_limit);
    ##$response->addParameters($message_params);
    #### setting status URI for response
    $self->eventTypes->status->operation('setupdata'); 
    ####  
    ### hashref to hold relation between metaID DB key and meta id 
    my $metaids = {}; 
    ###  hashref to contain time selects metadata which will be 
    ###  chained to the response metadata with pinger key (to re-use time range selects from request)
    my  $time_selects = {}; 
    $logger->debug(" Whole message : " . $self->asString);
    ### single data only ( per event ) set filters array as well
    my  $data  = $self->data->[0];
    my $requestmd =  $self->metadata->[0];
    my @filters = $self->filters?@{$self->filters}:();
     
    unless(  $requestmd  ) {
         $logger->error("Orphaned data   - no  metadata found for metadataRefid:" . $data->metadataIdRef );
         return; 
    } else {
         $logger->debug("Found metadata in request id=" .  $requestmd->id . " metadata=" . $requestmd->asString );
    }
    ### objects hashref  returned by backend query with metaID as key
    my  $objects_hashref  = {};
	# if there is a Key, then get data arrayref
     if($requestmd->key  && $requestmd->key->id) {      
	    if($self->_retriveDataByKey({md => $requestmd , datas => $objects_hashref,  timeselects => $time_selects, metaids => $metaids, response => $response})) {
	        $response->addResultResponse({md => $requestmd, message => 'no  matching data found',  eventType => $self->eventTypes->status->failure});		   
	        return $response;
	    }
     }   elsif($requestmd->subject | $requestmd->parameters) {
	        my $query = {}; 
	        my  $md_objects = undef;
	        eval {
		     foreach my $supplied_md ( $requestmd, @filters)  {
	                %{$query} =  (%{$query} , %{$self->buildQuery('eq',  $supplied_md)});		    
		     }  
		     $logger->debug(" Will query = " . Dumper $query);
	             unless($query && $query->{query_metaData} &&  ref($query->{query_metaData}) eq 'ARRAY' && scalar @{$query->{query_metaData}} >= 1) {
	                $logger->warn(" Nothing to query about for md=" . $requestmd->id);
		        $response->addResultResponse({ md => $requestmd, message => 'Nothing to query about(no key or parameters supplied)',  eventType => $self->eventTypes->status->failure});	
                        return $response;
	            }
                    $md_objects  = $self->DBO->getMeta($query->{query_metaData}, _mdSetLimit($query->{query_limit}) );
	        };
	        if($EVAL_ERROR) {
	            $logger->fatal("PingER backend  Query failed: " . $EVAL_ERROR);
		    die " System error, store failed";
	        }
	        my $timequery =  $self->processTime({timehash => $query->{time}});
	      
		  if($md_objects &&  (ref($md_objects) eq 'HASH') &&   %{$md_objects}) { 
	            foreach my $metaid  (sort { $a <=> $b} keys %{$md_objects}) {   
		        my $md =  $self->_ressurectMd( { md_row =>  $md_objects->{$metaid}});
		        if($self->_retriveDataByKey({key =>  $metaid,   timequery => $timequery, timeselects => $time_selects,
			                             metaids => $metaids, datas => $objects_hashref, response => $response}) ){
			    $response->addResultResponse({md => $md, message => 'no  matching data found',  eventType => $self->eventTypes->status->failure});		   
	                    return $response;
	                }
		    }		     
	        } else {
	            $response->addResultResponse({md => $requestmd, message => 'no metadata found',  eventType => $self->eventTypes->status->failure});		   
	        }  
            } else {
	        $response->addResultResponse({md =>$requestmd,  message => 'no key and no select in metadata  submitted',  eventType => $self->eventTypes->status->failure});    
	    }   
    
        if($objects_hashref && ref($objects_hashref) eq 'HASH') {
	    ############################################################   here add all those found data elements
           
            foreach my $metaid (keys %{$objects_hashref}) {     
	        if(%{$objects_hashref->{$metaid}}) { 
	            my $data  = perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data->new({id => "data". $response->dataID, metadataIdRef => "meta" . $metaids->{$metaid}});
	            foreach my $data_metaid (sort { $a <=> $b} keys %{$objects_hashref->{$metaid}}) {   
 	                foreach my  $timev (sort { $a <=> $b} keys %{$objects_hashref->{$metaid}->{$data_metaid}}) {   	         
                          $logger->debug(" What is data row :" .  Data::Dumper::Dumper $objects_hashref->{$metaid}->{$data_metaid}->{ $timev });
		          my $ctime = perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Data::CommonTime->new({ type => 'unix', value => $timev});
		       
		          foreach my $entry (qw/minRtt maxRtt medianRtt meanRtt clp iqrIpd lostPercent  maxIpd minIpd meanIpd duplicates outOfOrder/) {
		              next  unless  $objects_hashref->{$metaid}->{$data_metaid}->{ $timev }->{$entry};
		              my $value =  $objects_hashref->{$metaid}->{$data_metaid}->{ $timev }->{$entry};
		              my $datum = perfSONAR_PS::Datatypes::v2_0::pinger::Message::Data::CommonTime::Datum->new({name => $entry , value  =>  $value});
		              $ctime->addDatum($datum); 
	                  } 
		          $data->addCommonTime($ctime);
		        }
	            }
	            $response->addData($data); ## 
	            $response->add_dataID; 
	        } 
            } 
        }  
      
   
   return  $response;

}
#  auxiliary private function
#  check if setLimit was requested in the query
#  if not then set default 
#
sub _mdSetLimit {
    my $query = shift; 
    if($query  && ref($query) eq 'ARRAY' 
		       && $query->[0] &&  ref($query->[0]) eq 'HASH' 
		       && $query->[0]->{setLimit} && ref($query->[0]->{setLimit}) eq 'HASH'  
		       && $query->[0]->{setLimit}->{eq} && $query->[0]->{setLimit}->{eq} >= 1) {		    
       return   $query->[0]->{setLimit}->{eq};
    }
    return $_sizeLimit;
}
 
#
#  auxiliary private function
#  
#  accepting SQL row or metaID and will create md element for response and return it as object
#
#
sub _ressurectMd {
    my ($self, $params)  = @_; 
    my $logger  = get_logger( CLASSPATH ); 
    unless($params && ref($params) eq 'HASH' &&    ($params->{md_row} ||  $params->{metaID})) {
        $logger->error("Parameters missed:  md_row or metaID  are  required parameters");
        return ' Parameters missed:  md_row , timequery are required parameters';
    } 
    my $md = undef;
    
    if($params->{metaID}) {
        eval{
	    ($params->{metaID}, $params->{md_row})  = each %{$self->DBO->getMeta( [ 'metaID' , {'eq' => $params->{metaID}}], 1 )};
        };
	if($EVAL_ERROR)  {
	  $logger->logdie(" Fatal error while calling Rose::DB object query". $EVAL_ERROR);
	}
    } 
  
    my $metaid =  $params->{metaID};
    $md  = perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata->new(); 
    my $key =  perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Key->new({id => "meta$metaid" });
   
    my $subject = perfSONAR_PS::Datatypes::v2_0::pinger::Message::Metadata::Subject->new({ id => "subj$metaid",
  		      endPointPair => perfSONAR_PS::Datatypes::v2_0::nmwgt::Message::Metadata::Subject::EndPointPair->new({		      
  		                          src => perfSONAR_PS::Datatypes::v2_0::nmwgt::Message::Metadata::Subject::EndPointPair::Src->new({ value =>  $params->{md_row}->{ip_name_src}, type => 'hostname'}),
     				          dst => perfSONAR_PS::Datatypes::v2_0::nmwgt::Message::Metadata::Subject::EndPointPair::Dst->new({ value =>  $params->{md_row}->{ip_name_dst}, type => 'hostname'})
     				      }) 
     		  });		  
     
    my $pinger_params =   perfSONAR_PS::Datatypes::v2_0::pinger::Message::Metadata::Parameters->new({ id => "params$metaid" });
    my $param_arrref = []; 
    no strict 'refs';
      
    foreach my $pparam (qw/count packetSize ttl transport packetInterval protocol/) { 
        if($params->{md_row}->{$pparam}) {
  	   push @{$param_arrref} , perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Parameters::Parameter->new({name =>  $pparam , value => $params->{md_row}->{$pparam}});
        }
    }
    use strict;
    $md->subject($subject); 
    if($param_arrref && @{$param_arrref}) {
        $pinger_params->parameter($param_arrref);
        $md->parameters($pinger_params);
    }
    $md->key($key); 
    return $md;   
}
#  auxiliary private  function
#
#  _createTimeSelect
#
#  creates time range select metadata and store it for reuse in timeselects hashref
#
sub _createTimeSelect {
    my ($self, $params) = @_; 
    my $logger  = get_logger( CLASSPATH ); 
    unless($params->{timequery} && (ref($params->{timequery}) eq 'HASH') && $params->{response} && $params->{timeselects} ) {
        $logger->error("Parameters missed:    timequery,  response and timeselects are  required parameters");
        return ' Parameters missed:  timequery,  response and timeselects are required parameters';
    } 
    my $id = $params->{timequery}->{eq}?"eq=".$params->{timequery}->{eq}:"lt=".$params->{timequery}->{lt}."_gt=".$params->{timequery}->{gt};
   
    return $params->{timeselects}{$id}{md} if($params->{timeselects}{$id} &&  $params->{timeselects}{$id}{md});
    my $selects = [];
    if($params->{timequery}->{eq}) {
        push @{$selects} , perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Parameters::Parameter->new({name => 'timeStamp', value =>  $params->{timequery}->{eq}});
    } elsif($params->{timequery}->{gt} && $params->{timequery}->{lt}) {
        push @{$selects} , perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Parameters::Parameter->new({name => 'startTime', value =>  $params->{timequery}->{gt}});
        push @{$selects} , perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata::Parameters::Parameter->new({name => 'endTime', value =>  $params->{timequery}->{lt}});
    }  
    if($selects  && @{$selects}) {
        my $md =   perfSONAR_PS::Datatypes::v2_0::nmwg::Message::Metadata->new();
	$md->subject(perfSONAR_PS::Datatypes::v2_0::select::Message::Metadata::Subject->new({id => "subj" .  $params->{response}->mdID}));
	
        my $param_selects = perfSONAR_PS::Datatypes::v2_0::select::Message::Metadata::Parameters->new({ id => "params".$params->{response}->mdID,   parameter => $selects});
  	$md->parameters($param_selects); 
	$logger->debug("--------- Created New time select: ". $md->asString);
        $params->{timeselects}{$id}{md} = $params->{response}->addIDMetadata($md,  $self->eventTypes->ops->select);  
	return   $params->{timeselects}{$id}{md}; 
    } 
    return undef;
}


#
#  auxiliary private  function
#
#  this one will find time range from md, find proper list of data tables and will query data tables by metaID key
#  parameters - hashref to { md => $metadata_obj, datas => $iterators_attayref, key => $metaKey, timequery => $timequery_hashref, tables => $tables_hashref, response => response}
#  return 0 if everything OK, result will be added as data objects arrayref to the {datas} arrayref
#  return 1 if something is wrong
#
sub _retriveDataByKey {
    my ( $self, $params)  = @_; 
    my $logger  = get_logger( CLASSPATH );  
    unless($params && ref($params) eq 'HASH' && ($params->{key} || ($params->{md} && blessed $params->{md}))  && 
           $params->{response} && blessed $params->{response} && $params->{metaids} && $params->{datas} && $params->{timeselects}) {
        $logger->error("Parameters missed:  md,response,iterators,metaids,timeselects are required parameters");
        return -1;
    } 
    my $keyid = $params->{key}?$params->{key}:($params->{md} && blessed $params->{md})?$params->{md}->key->id:$logger->(" md or key MUST be provided");
    return ' Key parameter  missed ' unless $keyid;
    $params->{timequery}= $self->processTime( { element => $params->{md} } ) unless  $params->{timequery};
        
    unless( $params->{timequery} && ref($params->{timequery} ) eq 'HASH') {
        $logger->error("No time range in the query found");
        $params->{response}->addResultResponse({ md =>  $params->{md}, message =>  'No time range in the query found' ,  eventType => $self->eventTypes->status->failure});	
        return -1;
    } 
 
    my @timestamp_conditions = map { ('timestamp' => { $_ => $params->{timequery}->{$_} } )} keys %{$params->{timequery}};     
    my  $iterator_local =  $self->DBO->getData( [ metaID => {eq =>  $keyid},  @timestamp_conditions], undef, $_sizeLimit);
    if(%{$iterator_local}) {
        $params->{datas}->{$keyid} = $iterator_local;
        my $idref = $self->_createTimeSelect( $params );
	$logger->debug(" Created Time Select ..... ......id=$idref");	 
	my $pinger_md = $self->_ressurectMd({metaID =>  $keyid});
	$pinger_md->metadataIdRef($idref) if $idref; 
	$params->{metaids}->{$keyid} =   $params->{response}->addIDMetadata($pinger_md,  $self->eventTypes->tools->pinger); 
        return 0;
    } 
    return -1;
    
}
 
=head1 AUTHORS

   Maxim Grigoriev (FNAL)   2007-2008

=cut
}

1;