1## Domain Registry Interface, Registry object
2##
3## Copyright (c) 2005-2010 Patrick Mevzek <netdri@dotandco.com>. All rights reserved.
4##
5## This file is part of Net::DRI
6##
7## Net::DRI is free software; you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published by
9## the Free Software Foundation; either version 2 of the License, or
10## (at your option) any later version.
11##
12## See the LICENSE file that comes with this distribution for more details.
13#
14#
15#
16####################################################################################################
17
18package Net::DRI::Registry;
19
20use strict;
21use warnings;
22
23use base qw(Class::Accessor::Chained::Fast Net::DRI::BaseClass);
24__PACKAGE__->mk_ro_accessors(qw(name driver profile trid_factory logging)); ## READ-ONLY !!
25
26use Time::HiRes ();
27
28use Net::DRI::Exception;
29use Net::DRI::Util;
30use Net::DRI::Protocol::ResultStatus;
31use Net::DRI::Data::RegistryObject;
32
33our $AUTOLOAD;
34
35our $VERSION=do { my @r=(q$Revision: 1.32 $=~/\d+/g); sprintf("%d".".%02d" x $#r, @r); };
36
37=pod
38
39=head1 NAME
40
41Net::DRI::Registry - Specific Registry Driver Instance inside Net::DRI
42
43=head1 DESCRIPTION
44
45Please see the README file for details.
46
47=head1 SUPPORT
48
49For now, support questions should be sent to:
50
51E<lt>netdri@dotandco.comE<gt>
52
53Please also see the SUPPORT file in the distribution.
54
55=head1 SEE ALSO
56
57E<lt>http://www.dotandco.com/services/software/Net-DRI/E<gt>
58
59=head1 AUTHOR
60
61Patrick Mevzek, E<lt>netdri@dotandco.comE<gt>
62
63=head1 COPYRIGHT
64
65Copyright (c) 2005-2010 Patrick Mevzek <netdri@dotandco.com>.
66All rights reserved.
67
68This program is free software; you can redistribute it and/or modify
69it under the terms of the GNU General Public License as published by
70the Free Software Foundation; either version 2 of the License, or
71(at your option) any later version.
72
73See the LICENSE file that comes with this distribution for more details.
74
75=cut
76
77####################################################################################################
78
79sub new
80{
81 my ($class,$name,$drd,$cache,$trid,$logging)=@_;
82
83 my $self={name   => $name,
84           driver => $drd,
85           cache  => $cache,
86           profiles => {}, ## { profile name => { protocol  => X
87                           ##                     transport => X
88                           ##                     status    => Net::DRI::Protocol::ResultStatus
89                           ##                     %extra
90                           ##                   }
91                           ## }
92           profile => undef, ## current profile
93           auto_target => {},
94           last_data => {},
95	   last_process => {},
96           trid_factory => $trid,
97           logging => $logging,
98          };
99
100 bless($self,$class);
101 return $self;
102}
103
104sub available_profile
105{
106 my $self=shift;
107 return (defined($self->{profile}))? 1 : 0;
108}
109
110sub available_profiles
111{
112 my ($self,$full)=@_;
113 $full||=0;
114 return sort($full ? map { $_->{fullname} } values(%{$self->{profiles}}) : keys(%{$self->{profiles}}));
115}
116
117sub exist_profile
118{
119 my ($self,$name)=@_;
120 return (defined($name) && exists($self->{profiles}->{$name}));
121}
122
123sub err_no_current_profile           { Net::DRI::Exception->die(0,'DRI',3,'No current profile available'); }
124sub err_profile_name_does_not_exist  { Net::DRI::Exception->die(0,'DRI',4,'Profile name '.$_[0].' does not exist'); }
125
126sub remote_object
127{
128 my $self=shift;
129 return Net::DRI::Data::RegistryObject->new($self,@_);
130}
131
132sub _current
133{
134 my ($self,$what,$tostore)=@_;
135 err_no_current_profile()                          unless (defined($self->{profile}));
136 err_profile_name_does_not_exist($self->{profile}) unless (exists($self->{profiles}->{$self->{profile}}));
137 Net::DRI::Exception::err_method_not_implemented($what) unless (exists($self->{profiles}->{$self->{profile}}->{$what}));
138
139 if (($what eq 'status') && $tostore)
140 {
141  $self->{profiles}->{$self->{profile}}->{$what}=$tostore;
142 }
143
144 return $self->{profiles}->{$self->{profile}}->{$what};
145}
146
147sub transport { return shift->_current('transport'); }
148sub protocol  { return shift->_current('protocol');  }
149sub status    { return shift->_current('status',@_); }
150sub protocol_transport { my $self=shift; return ($self->protocol(),$self->transport()); }
151
152sub local_object
153{
154 my $self=shift;
155 my $f=shift;
156 return unless $self && $f;
157 return $self->_current('protocol')->create_local_object($f,@_);
158}
159
160sub _result
161{
162 my ($self,$f)=@_;
163 my $p=$self->profile();
164 err_no_current_profile() unless (defined($p));
165 Net::DRI::Exception->die(0,'DRI',6,'No last status code available for current registry and profile') unless (exists($self->{profiles}->{$p}->{status}));
166 my $rc=$self->{profiles}->{$p}->{status}; ## a Net::DRI::Protocol::ResultStatus object !
167 Net::DRI::Exception->die(1,'DRI',5,'Status key is not a Net::DRI::Protocol::ResultStatus object') unless UNIVERSAL::isa($rc,'Net::DRI::Protocol::ResultStatus');
168 return $rc if ($f eq 'self');
169 Net::DRI::Exception->die(1,'DRI',5,'Method '.$f.' not implemented in Net::DRI::Protocol::ResultStatus') unless ($f && $rc->can($f));
170 return $rc->$f();
171}
172
173sub result_is_success  { return shift->_result('is_success');  }
174sub is_success         { return shift->_result('is_success');  } ## Alias
175sub result_code        { return shift->_result('code');        }
176sub result_native_code { return shift->_result('native_code'); }
177sub result_message     { return shift->_result('message');     }
178sub result_lang        { return shift->_result('lang');        }
179sub result_status      { return shift->_result('self');        }
180sub result_extra_info  { return shift->_result('info');        }
181
182sub cache_expire { return shift->{cache}->delete_expired(); }
183sub cache_clear  { return shift->{cache}->delete(); }
184
185sub set_info
186{
187 my ($self,$type,$key,$data,$ttl)=@_;
188 my $p=$self->profile();
189 err_no_current_profile() unless defined($p);
190 my $regname=$self->name();
191
192 my $c=$self->{cache}->set($regname.'.'.$p,$type,$key,$data,$ttl);
193 $self->{last_data}=$c; ## the hash exists, since we called clear_info somewhere before
194
195 return $c;
196}
197
198## Returns a $rc object or undef if nothing found in cache for the specific object ($type/$key) and action ($action)
199sub try_restore_from_cache
200{
201 my ($self,$type,$key,$action)=@_;
202 if (! Net::DRI::Util::all_valid($type,$key,$action)) { Net::DRI::Exception::err_assert('try_restore_from_cache improperly called'); }
203
204 my $a=$self->get_info('action',$type,$key);
205 ## not in cache or in cache but for some other action
206 if (! defined $a || ($a ne $action)) { $self->log_output('debug','core',sprintf('Cache MISS (empty cache or other action) for type=%s key=%s',$type,$key)); return; }
207
208 ## retrieve from cache, copy, and do some cleanup
209 $self->{last_data}=$self->get_info_all($type,$key);
210 ## since we passed the above test on get_info('action'), we know here we received something defined by get_info_all,
211 ## but we test explicitely again (get_info_all returns an empty ref hash on problem, not undef), to avoid race conditions and such
212 if (! keys(%{$self->{last_data}})) { $self->log_output('debug','core',sprintf('Cache MISS (no last_data content) for type=%s key=%s',$type,$key)); return; }
213
214 ## get_info_all makes a copy, but only at first level ! so this high level change is ok (no pollution), but be warned for below !
215 $self->{last_data}->{result_from_cache}=1;
216
217 ## however we must take care of what we do in levels further below, as the same data is probably in the original $rc object (if not thrown away by application)
218 my $rd=$self->{last_data}->{result_status}->get_data_collection();
219
220 ## we first make a copy (here it is a plain ref hash, no objects inside, otherwise a proper clone() would be needed, see Clone::* modules), then we can update it.
221 ## If something more complex is needed, a proper clone() should be implemented
222 $rd->{session}={ %{$rd->{session}} };
223 ## (if there are other keys than exchange, we do not need to copy them, since we do not change their content)
224 $rd->{session}->{exchange}={ %{$rd->{session}->{exchange}} };
225 $rd->{session}->{exchange}->{result_from_cache}=1;
226 ## Should we delete the raw exchange (session/exchange/command,duration,reply) data too ?
227
228 $self->log_output('debug','core',sprintf('Cache HIT for type=%s key=%s',$type,$key));
229 return $self->get_info('result_status');
230}
231
232sub clear_info { shift->{last_data}={}; }
233
234sub get_info
235{
236 my ($self,$what,$type,$key)=@_;
237 return unless (defined($what) && $what);
238
239 if (Net::DRI::Util::all_valid($type,$key)) ## search the cache, by default same registry & profile !
240 {
241  my $p=$self->profile();
242  err_no_current_profile() unless defined($p);
243  my $regname=$self->name();
244  return $self->{cache}->get($type,$key,$what,$regname.'.'.$p);
245 } else
246 {
247  return unless exists($self->{last_data}->{$what});
248  return $self->{last_data}->{$what};
249 }
250}
251
252sub get_info_all
253{
254 my ($self,$type,$key)=@_;
255 my $rh;
256
257 if (Net::DRI::Util::all_valid($type,$key))
258 {
259  my $p=$self->profile();
260  err_no_current_profile() unless defined($p);
261  my $regname=$self->name();
262  $rh=$self->{cache}->get($type,$key,undef,$regname.'.'.$p);
263 } else
264 {
265  $rh=$self->{last_data};
266 }
267
268 return {} unless (defined($rh) && ref($rh) && keys(%$rh));
269
270 my %h=%{ $rh }; ## create a copy, as we will delete content... ## BUGFIX !!
271 foreach my $k (grep { /^_/ } keys(%h)) { delete($h{$k}); }
272 return \%h;
273}
274
275sub get_info_keys
276{
277 my ($self,$type,$key)=@_;
278 my $rh=$self->get_info_all($type,$key);
279 return sort { $a cmp $b } keys(%$rh);
280}
281
282####################################################################################################
283## Change profile
284sub target
285{
286 my ($self,$profile)=@_;
287 err_profile_name_does_not_exist($profile) unless ($profile && exists($self->{profiles}->{$profile}));
288 $self->{profile}=$profile;
289}
290
291sub profile_auto_switch
292{
293 my ($self,$otype,$oaction)=@_;
294 my $p=$self->get_auto_target($otype,$oaction);
295 return unless defined($p);
296 $self->target($p);
297 return;
298}
299
300sub set_auto_target
301{
302 my ($self,$profile,$otype,$oaction)=@_; ## $otype/$oaction may be undef
303 err_profile_name_does_not_exist($profile) unless ($profile && exists($self->{profiles}->{$profile}));
304
305 my $rh=$self->{auto_target};
306 $otype||='_default';
307 $oaction||='_default';
308 $rh->{$otype}={} unless (exists($rh->{$otype}));
309 $rh->{$otype}->{$oaction}=$profile;
310}
311
312sub get_auto_target
313{
314 my ($self,$otype,$oaction)=@_;
315 my $at=$self->{auto_target};
316 $otype='_default' unless (exists($at->{$otype}));
317 return unless (exists($at->{$otype}));
318 my $ac=$at->{$otype};
319 return unless (defined($ac) && ref($ac));
320 $oaction='_default' unless (exists($ac->{$oaction}));
321 return unless (exists($ac->{$oaction}));
322 return $ac->{$oaction};
323}
324
325sub add_current_profile
326{
327 my ($self,@p)=@_;
328 my $rc=$self->add_profile(@p);
329 if ($rc->is_success()) { $self->target($p[0]); }
330 return $rc;
331}
332
333## Transport and Protocol parameters are merged (semantically but not chronologically, parameters coming later erase previous ones) in this order;
334## - TransportConnectionClass->transport_default() [only for transport parameters]
335## - Protocol->transport_default() [only for transport parameters]
336## - DRD->transport_protocol_default()
337## - user specified parameters to add_profile (they always have precedence over defaults stored in the 3 previous cases)
338
339## API: profile name, profile type (types starting with "test=" are only for internal tests, and should not be used in production), transport params {}, protocol params {}
340sub add_profile
341{
342 my ($self,$name,$type,$trans_p,$prot_p)=@_;
343
344 if (! Net::DRI::Util::all_valid($name,$type))   { Net::DRI::Exception::usererr_insufficient_parameters('add_profile needs at least 2 parameters: new profile name and type'); }
345 if (defined $trans_p && ref $trans_p ne 'HASH') { Net::DRI::Exception::usererr_invalid_parameters('If provided, 3rd parameter of add_profile (transport data) must be a ref hash'); }
346 if (defined $prot_p  && ref $prot_p ne 'HASH')  { Net::DRI::Exception::usererr_invalid_parameters('If provided, 4th parameter of add_profile (protocol data) must be a ref hash'); }
347 if ($self->exist_profile($name))                { Net::DRI::Exception::usererr_invalid_parameters('New profile name "'.$name.'" already in use'); }
348
349 my $drd=$self->driver();
350 my ($test)=($type=~s/^test=//)? 1 : 0;
351 my ($tc,$tp,$pc,$pp); ## Transport Class, Transport Params, Protocol Class, Protocol Params
352 ($tc,$tp,$pc,$pp)=$drd->transport_protocol_default($type) if (!$test || $type!~m/[A-Z]/);
353 if ($test)
354 {
355  $self->log_output('emergency','core','For profile "'.$name.'", using INTERNAL TESTING configuration! This should not happen in production, but only during "make test"!');
356  Net::DRI::Exception::err_assert('test profile types are to be used only during internal tests') unless exists $INC{'Test/More.pm'};
357  $tc='Dummy';
358  $tp=$trans_p;
359  $trans_p=undef;
360  if ($type=~m/[A-Z]/)
361  {
362   $pc=$type;
363   $pp=defined $prot_p ? $prot_p : {};
364   $prot_p=undef;
365  }
366 }
367 if (!Net::DRI::Util::all_valid($tc,$tp,$pc,$pp) || ref $tp ne 'HASH' || ref $pp ne 'HASH') { Net::DRI::Exception::usererr_invalid_parameters(sprintf('Registry "%s" does not provide profile type "%s")',$self->name(),$type)); }
368
369 $tp={ %$tp, %$trans_p } if defined $trans_p;
370 $pp={ %$pp, %$prot_p  } if defined $prot_p;
371 $tc='Net::DRI::Transport::'.$tc unless ($tc=~m/::/);
372 $pc='Net::DRI::Protocol::'.$pc  unless ($pc=~m/::/);
373
374 $drd->transport_protocol_init($type,$tc,$tp,$pc,$pp,$test) if $drd->can('transport_protocol_init');
375
376 $tc->require() or Net::DRI::Exception::err_failed_load_module('DRI',$tc,$@);
377 $pc->require() or Net::DRI::Exception::err_failed_load_module('DRI',$pc,$@);
378 $self->log_output('debug','core',sprintf('For profile "%s" attempting to initialize transport "%s" and protocol "%s"',$name,$tc,$pc));
379
380 my $po=$pc->new($drd,$pp); ## Protocol must come first, as it may be needed during transport setup; it should not die
381 $tp={ $po->transport_default(), %$tp } if ($po->can('transport_default'));
382
383 my $to;
384 eval
385 {
386  $to=$tc->new({registry=>$self,profile=>$name,protocol=>$po},$tp); ## this may die !
387 };
388 if ($@) ## some kind of error happened
389 {
390  return $@ if (ref($@) eq 'Net::DRI::Protocol::ResultStatus');
391  $@=Net::DRI::Exception->new(1,'internal',0,'Error not handled: '.$@) unless ref($@);
392  die($@);
393 }
394
395 my $fullname=sprintf('%s (%s/%s + %s/%s)',$name,$po->name(),$po->version(),$to->name(),$to->version());
396 $self->{profiles}->{$name}={ fullname => $fullname, transport => $to, protocol => $po, status => undef };
397 $self->log_output('notice','core','Successfully added profile "'.$fullname.'"');
398 return Net::DRI::Protocol::ResultStatus->new_success('COMMAND_SUCCESSFUL','Profile "'.$name.'" added successfully');
399}
400
401sub del_profile
402{
403 my ($self,$name)=@_;
404 if (defined($name))
405 {
406  err_profile_name_does_not_exist($name) unless ($self->exist_profile($name));
407 } else
408 {
409  err_no_current_profile() unless (defined($self->{profile}));
410  $name=$self->{profile};
411 }
412
413 my $p=$self->{profiles}->{$name};
414 $p->{protocol}->end()  if (ref($p->{protocol})  && $p->{protocol}->can('end'));
415 $p->{transport}->end({registry => $self, profile => $name}) if (ref($p->{transport}) && $p->{transport}->can('end'));
416 delete($self->{profiles}->{$name});
417 $self->{profile}=undef if $self->{profile} eq $name; ## current profile is not defined anymore
418 return Net::DRI::Protocol::ResultStatus->new_success('COMMAND_SUCCESSFUL','Profile '.$name.' deleted successfully');
419}
420
421sub end
422{
423 my $self=shift;
424 foreach my $name (keys(%{$self->{profiles}}))
425 {
426  my $p=$self->{profiles}->{$name};
427  $p->{protocol}->end()  if (ref($p->{protocol})  && $p->{protocol}->can('end'));
428  $p->{transport}->end() if (ref($p->{transport}) && $p->{transport}->can('end'));
429  delete $self->{profiles}->{$name}
430 }
431
432 $self->{driver}->end() if $self->{driver}->can('end');
433}
434
435sub can
436{
437 my ($self,$what)=@_;
438 return $self->UNIVERSAL::can($what) || $self->driver->can($what);
439}
440
441####################################################################################################
442####################################################################################################
443
444sub has_action
445{
446 my ($self,$otype,$oaction)=@_;
447 my ($po,$to)=$self->protocol_transport();
448 return $po->has_action($otype,$oaction);
449}
450
451sub process
452{
453 my ($self,$otype,$oaction)=@_[0,1,2];
454 my $pa=$_[3] || []; ## store them ?
455 my $ta=$_[4] || [];
456 $self->{last_process}=[$otype,$oaction,$pa,$ta]; ## should be handled more generally by LocalStorage/Exchange
457
458 ## Automated switch, if enabled
459 $self->profile_auto_switch($otype,$oaction);
460
461 ## Current protocol/transport objects for current profile
462 my ($po,$to)=$self->protocol_transport();
463 my $trid=$self->generate_trid();
464 my $ctx={trid => $trid, otype => $otype, oaction => $oaction, phase => 'active' };
465 my $tosend;
466
467 eval { $tosend=$po->action($otype,$oaction,$trid,@$pa); }; ## TODO : this may need to be pushed in loop below if we need to change message to send when failure
468 return $self->format_error($@) if $@;
469
470 $self->{ops}->{$trid}=[0,$tosend,undef]; ## 0 = todo, not sent ## This will be done in/with LocalStorage
471 my $timeout=$to->timeout();
472 my $prevalarm=alarm(0); ## removes current alarm
473 my $pause=$to->pause();
474 my $start=Time::HiRes::time();
475 $self->{ops}->{$trid}->[2]=$start;
476
477 my $count=0;
478 my $r;
479 while (++$count <= $to->retry())
480 {
481  $self->log_output('debug','core',sprintf('New process loop iteration for TRID=%s with count=%d pause=%f timeout=%f',$trid,$count,$pause,$timeout));
482  Time::HiRes::sleep($pause) if (defined($pause) && $pause && ($count > 1));
483  $self->log_output('warning','core',sprintf('Starting try #%d for TRID=%s',$count,$trid)) if $count>1;
484  $r=eval
485  {
486   local $SIG{ALRM}=sub { die 'timeout' };
487   alarm($timeout) if ($timeout);
488   $self->log_output('debug','core',sprintf('Attempting to send data for TRID=%s',$trid));
489   ## Should we also pass the current registry driver (or at least its name), and the current profile name ? This may be useful in logging
490   $to->send($ctx,$tosend,$count,$ta); ## either success or exception, no result code
491   $self->log_output('debug','core','Successfully sent data to registry for TRID='.$trid);
492   $self->{ops}->{$trid}->[0]=1; ## now it is sent
493   return $self->process_back($trid,$po,$to,$otype,$oaction,$count) if $to->is_sync();
494   my $rc=Net::DRI::Protocol::ResultStatus->new_success('COMMAND_SUCCESSFUL_PENDING');
495   $rc->_set_trid([ $trid ]);
496   $self->status($rc);
497   return $rc;
498  };
499  alarm(0) if ($timeout); ## removes our alarm
500  if ($@) ## some die happened inside the eval
501  {
502   return $self->format_error($@) if (ref($@) eq 'Net::DRI::Protocol::ResultStatus'); ## should probably be a return here see below TODOXXX
503   my $is_timeout=(!ref($@) && ($@=~m/timeout/))? 1 : 0;
504   $@=$is_timeout? Net::DRI::Exception->new(1,'transport',1,'timeout') : Net::DRI::Exception->new(1,'internal',0,'Error not handled: '.$@) unless ref($@);
505   $self->log_output('debug','core',$is_timeout? 'Got timeout for TRID='.$trid : 'Got error for TRID='.$trid.' : '.$@->as_string());
506   next if $to->try_again($ctx,$po,$@,$count,$is_timeout,$self->{ops}->{$trid}->[0],\$pause,\$timeout); ## will determine if 1) we break now the loop/we propagate the error (fatal error) 2) we retry
507   die($@);
508  }
509  last if defined($r);
510 } ## end of while
511 alarm($prevalarm) if $prevalarm; ## re-enable previous alarm (warning, time is off !!)
512 Net::DRI::Exception->die(0,'transport',1,sprintf('Unable to communicate with registry after %d tries for a total delay of %.03f seconds',$to->retry(),Time::HiRes::time()-$start)) unless defined $r;
513 return $r;
514}
515
516sub format_error
517{
518 my ($self,$err)=@_;
519 if (ref($err) eq 'Net::DRI::Protocol::ResultStatus')
520 {
521  $self->status($err); ## should that be done above also ? TODOXXX
522  return $err;
523 }
524 $err=Net::DRI::Exception->new(1,'internal',0,'Error not handled: '.$err) unless ref($err);
525 die($err);
526}
527
528## also called directly , when we found something to do for asynchronous case, through TRID (TODO)
529## We are already in an eval here, and a while loop for retries
530sub process_back
531{
532 my ($self,$trid,$po,$to,$otype,$oaction,$count)=@_;
533 my $ctx={trid => $trid, otype => $otype, oaction => $oaction }; ## How will we fill that in case of async operation (direct call here) ?
534 my ($rc,$ri,$oname);
535
536 $self->log_output('debug','core','Attempting to receive data from registry for TRID='.$trid);
537 my $res=$to->receive($ctx,$count); ## a Net::DRI::Data::Raw or die inside
538 my $stop=Time::HiRes::time();
539 $self->log_output('debug','core','Successfully received data from registry for TRID='.$trid);
540 Net::DRI::Exception->die(0,'transport',5,'Unable to receive message from registry') unless defined($res);
541 $self->{ops}->{$trid}->[0]=2; ## now it is received
542 $self->clear_info(); ## make sure we will overwrite current latest info
543 $oname=_extract_oname($otype,$oaction,$self->{last_process}->[2]); ## lc() would be good here but this breaks a lot of things !
544 ($rc,$ri)=$po->reaction($otype,$oaction,$res,$self->{ops}->{$trid}->[1],$oname); ## $tosend needed to propagate EPP version, for example
545 $rc->_set_trid([ $trid ]) unless $rc->trid(); ## if not done inside Protocol::*::Message::result_status, make sure we save at least our transaction id
546
547 if ($rc->is_closing() || (exists($ri->{_internal}) && exists($ri->{_internal}->{must_reconnect}) && $ri->{_internal}->{must_reconnect}))
548 {
549  $self->log_output('notice','core','Registry closed connection, we will automatically reconnect during next exchange');
550  $to->current_state(0);
551 }
552 delete($ri->{_internal});
553
554 ## Set latest status from what we got
555 $self->status($rc);
556
557 $ri->{session}->{exchange}->{result_from_cache}=0;
558 $ri->{session}->{exchange}->{protocol}=$po->name().'/'.$po->version();
559 $ri->{session}->{exchange}->{transport}=$to->name().'/'.$to->version();
560 $ri->{session}->{exchange}->{registry}=$self->name();
561 $ri->{session}->{exchange}->{profile}=$self->profile();
562 $ri->{session}->{exchange}->{trid}=$trid;
563
564 ## set_info stores also data in last_data, so we make sure to call last for current object
565 foreach my $type (keys(%$ri))
566 {
567  foreach my $key (keys(%{$ri->{$type}}))
568  {
569   next if ($oname && ($type eq $otype) && ($key eq $oname));
570   $self->set_info($type,$key,$ri->{$type}->{$key});
571  }
572 }
573
574 ## Now set the last info, the one regarding directly the object
575 if ($oname && $otype)
576 {
577  my $rli={ result_status => $rc };
578  $rli=$ri->{$otype}->{$oname} if (exists($ri->{$otype}) && exists($ri->{$otype}->{$oname})); ## result_status already done in Protocol
579  $self->set_info($otype,$oname,$rli);
580 }
581
582 ## Not before !
583 ## Remove all ResultStatus object, to avoid all circular references
584 foreach my $v1 (values(%$ri))
585 {
586  foreach my $v2 (values(%{$v1}))
587  {
588   delete($v2->{result_status}) if exists($v2->{result_status});
589  }
590 }
591
592 $ri->{session}->{exchange}={ %{$ri->{session}->{exchange}}, duration_seconds => $stop-$self->{ops}->{$trid}->[2], raw_command => $self->{ops}->{$trid}->[1]->as_string(), raw_reply => $res->as_string(), object_type => $otype, object_action => $oaction };
593 $ri->{session}->{exchange}->{object_name}=$oname if $oname;
594 $rc->_set_data($ri);
595 delete($self->{ops}->{$trid});
596 return $rc;
597}
598
599sub _extract_oname
600{
601 my ($otype,$oaction,$pa)=@_;
602
603 return 'domains' if ($otype eq 'account' && $oaction eq 'list_domains');
604 my $o=$pa->[0];
605 return 'session' unless defined($o);
606 $o=$o->[1] if (ref($o) eq 'ARRAY'); ## should be enough for _multi but still a little strange
607 return (Net::DRI::Util::normalize_name($otype,$o))[1] unless ref($o); ## ?? ## TODO ## this fails t/626nominet line 306
608 return (Net::DRI::Util::normalize_name('nsgroup',$otype eq 'nsgroup'? $o->name() : $o->get_details(1)))[1] if Net::DRI::Util::isa_hosts($o);
609 return $o->srid() if Net::DRI::Util::isa_contact($o);
610 return 'session';
611}
612
613####################################################################################################
614
615sub protocol_capable
616{
617 my ($ndr,$op,$subop,$action)=@_;
618 return 0 unless ($op && $subop); ## $action may be undefined
619 my $po=$ndr->protocol();
620 my $cap=$po->capabilities(); ## hashref
621
622 return 0 unless ($cap && (ref($cap) eq 'HASH') && exists($cap->{$op})
623                       && (ref($cap->{$op}) eq 'HASH') && exists($cap->{$op}->{$subop})
624                       && (ref($cap->{$op}->{$subop}) eq 'ARRAY'));
625
626 return 1 unless (defined($action) && $action);
627
628 foreach my $a (@{$cap->{$op}->{$subop}})
629 {
630  return 1 if ($a eq $action);
631 }
632 return 0;
633}
634
635sub log_output
636{
637 my ($self,$level,$where,$msg)=@_;
638 my $r=$self->name();
639 $r.='.'.$self->{profile} if (defined $self->{profile});
640 $msg='('.$r.') '.$msg;
641 return $self->SUPER::log_output($level,$where,$msg);
642}
643
644####################################################################################################
645
646sub AUTOLOAD
647{
648 my $self=shift;
649 my $attr=$AUTOLOAD;
650 $attr=~s/.*:://;
651 return unless $attr=~m/[^A-Z]/; ## skip DESTROY and all-cap methods
652
653 my $drd=$self->driver(); ## This is a DRD object
654 Net::DRI::Exception::err_method_not_implemented($attr.' in '.$drd) unless (ref($drd) && $drd->can($attr));
655 $self->log_output('debug','core',sprintf('Calling %s from Net::DRI::Registry',$attr));
656 return $drd->$attr($self,@_);
657}
658
659####################################################################################################
6601;
661