| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Elive::Connection; | 
| 2 | 15 |  |  | 15 |  | 142183 | use warnings; use strict; | 
|  | 15 |  |  | 15 |  | 34 |  | 
|  | 15 |  |  |  |  | 552 |  | 
|  | 15 |  |  |  |  | 75 |  | 
|  | 15 |  |  |  |  | 20 |  | 
|  | 15 |  |  |  |  | 738 |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | our $VERSION = '0.02'; | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 15 |  |  | 15 |  | 72 | use Carp; | 
|  | 15 |  |  |  |  | 22 |  | 
|  | 15 |  |  |  |  | 927 |  | 
| 7 | 15 |  |  | 15 |  | 90 | use File::Spec::Unix; | 
|  | 15 |  |  |  |  | 34 |  | 
|  | 15 |  |  |  |  | 386 |  | 
| 8 | 15 |  |  | 15 |  | 6566 | use HTML::Entities; | 
|  | 15 |  |  |  |  | 66987 |  | 
|  | 15 |  |  |  |  | 1430 |  | 
| 9 | 15 |  |  | 15 |  | 122 | use Scalar::Util; | 
|  | 15 |  |  |  |  | 23 |  | 
|  | 15 |  |  |  |  | 777 |  | 
| 10 |  |  |  |  |  |  | require SOAP::Lite; | 
| 11 | 15 |  |  | 15 |  | 4073 | use URI; | 
|  | 15 |  |  |  |  | 42792 |  | 
|  | 15 |  |  |  |  | 382 |  | 
| 12 | 15 |  |  | 15 |  | 88 | use URI::Escape qw{}; | 
|  | 15 |  |  |  |  | 22 |  | 
|  | 15 |  |  |  |  | 235 |  | 
| 13 | 15 |  |  | 15 |  | 4141 | use Try::Tiny; | 
|  | 15 |  |  |  |  | 8610 |  | 
|  | 15 |  |  |  |  | 800 |  | 
| 14 | 15 |  |  | 15 |  | 3360 | use YAML::Syck; | 
|  | 15 |  |  |  |  | 12087 |  | 
|  | 15 |  |  |  |  | 961 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 15 |  |  | 15 |  | 3151 | use parent qw{Class::Accessor Class::Data::Inheritable}; | 
|  | 15 |  |  |  |  | 1816 |  | 
|  | 15 |  |  |  |  | 100 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 | 15 |  |  | 15 |  | 34015 | use Elive; | 
|  | 15 |  |  |  |  | 31 |  | 
|  | 15 |  |  |  |  | 404 |  | 
| 19 | 15 |  |  | 15 |  | 75 | use Elive::Util; | 
|  | 15 |  |  |  |  | 20 |  | 
|  | 15 |  |  |  |  | 18592 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | =head1 NAME | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | Elive::Connection - Manage Elluminate Live SOAP connections. | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | This is an abstract class for managing connections and related resources. | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | Most of the time, you'll be dealing with specific class instances; See L L. | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | =cut | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | __PACKAGE__->mk_accessors( qw{url user pass _soap debug type timeout} ); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | =head1 METHODS | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | =cut | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | =head2 connect | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | my $sdk_c1 = Elive::Connection->connect('http://someserver.com/test', | 
| 42 |  |  |  |  |  |  | 'user1', 'pass1', debug => 1, | 
| 43 |  |  |  |  |  |  | ); | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | my $url1 = $sdk_c1->url;   #  'http://someserver.com/test' | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | my $sdk_c2 =  Elive::Connection->connect('http://user2:pass2@someserver.com/test'); | 
| 48 |  |  |  |  |  |  | my $url2 = $sdk_c2->url;   #  'http://someserver.com/test' | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | Establishes a logical SOAP connection. | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | =cut | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | sub connect { | 
| 55 | 0 |  |  | 0 |  |  | my ($class, $url, $user, $pass, %opt) = @_; | 
| 56 |  |  |  |  |  |  | # | 
| 57 |  |  |  |  |  |  | # default connection - for backwards compatibility | 
| 58 |  |  |  |  |  |  | # | 
| 59 | 0 |  |  |  |  |  | require Elive::Connection::SDK; | 
| 60 | 0 |  |  |  |  |  | return Elive::Connection::SDK->connect($url, $user => $pass, %opt); | 
| 61 |  |  |  |  |  |  | } | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | sub _connect { | 
| 64 | 0 |  |  | 0 |  |  | my ($class, $url, $user, $pass, %opt) = @_; | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 0 |  | 0 |  |  |  | my $debug = $opt{debug}||0; | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 0 |  |  |  |  |  | $url =~ s{/$}{}x; | 
| 69 |  |  |  |  |  |  |  | 
| 70 | 0 |  |  |  |  |  | my $uri_obj = URI->new($url); | 
| 71 |  |  |  |  |  |  |  | 
| 72 | 0 |  |  |  |  |  | my $userinfo = $uri_obj->userinfo; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 0 | 0 |  |  |  |  | if ($userinfo) { | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # | 
| 77 |  |  |  |  |  |  | # extract and remove any credentials from the url | 
| 78 |  |  |  |  |  |  | # | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 0 |  |  |  |  |  | my ($uri_user, $uri_pass) = split(':',$userinfo, 2); | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 0 | 0 |  |  |  |  | if ($uri_user) { | 
| 83 | 0 | 0 | 0 |  |  |  | if ($user && $user ne $uri_user) { | 
| 84 | 0 |  |  |  |  |  | carp 'ignoring user in URI scheme - overridden'; | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  | else { | 
| 87 | 0 |  |  |  |  |  | $user = URI::Escape::uri_unescape($uri_user); | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 0 | 0 |  |  |  |  | if ($uri_pass) { | 
| 92 | 0 | 0 | 0 |  |  |  | if ($pass && $pass ne $uri_pass) { | 
| 93 | 0 |  |  |  |  |  | carp 'ignoring pass in URI scheme - overridden'; | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  | else { | 
| 96 | 0 |  |  |  |  |  | $pass = URI::Escape::uri_unescape($uri_pass); | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  | else { | 
| 101 | 0 | 0 |  |  |  |  | warn "no credentials in url: $url" if $debug; | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 0 |  |  |  |  |  | my $uri_path = $uri_obj->path; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 0 | 0 |  |  |  |  | $pass = '' unless defined $pass; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 0 |  |  |  |  |  | my @path = File::Spec::Unix->splitdir($uri_path); | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 0 | 0 | 0 |  |  |  | shift (@path) | 
| 111 |  |  |  |  |  |  | if (@path && !$path[0]); | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 0 | 0 | 0 |  |  |  | pop (@path) | 
| 114 |  |  |  |  |  |  | if (@path && $path[-1] eq 'webservice.event'); | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | # | 
| 117 |  |  |  |  |  |  | # normalise the connection url by removing suffixes. The following | 
| 118 |  |  |  |  |  |  | # all reduce to http://mysite/myinst: | 
| 119 |  |  |  |  |  |  | # -- http://mysite/myinst/webservice.event | 
| 120 |  |  |  |  |  |  | # -- http://mysite/myinst/v2 | 
| 121 |  |  |  |  |  |  | # -- http://mysite/myinst/v2/webservice.event | 
| 122 |  |  |  |  |  |  | # -- http://mysite/myinst/default | 
| 123 |  |  |  |  |  |  | # -- http://mysite/myinst/default/webservice.event | 
| 124 |  |  |  |  |  |  | # | 
| 125 |  |  |  |  |  |  | # there's some ambiguity, an instance named v1 ... v9 will cause trouble! | 
| 126 |  |  |  |  |  |  | # | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 0 | 0 | 0 |  |  |  | if (@path && $path[-1] =~ m{^v(\d+)$}) { | 
| 129 | 0 | 0 |  |  |  |  | croak "unsupported standard bridge version v${1}, endpoint path: ". File::Spec::Unix->catdir(@path, 'webservice.event') | 
| 130 |  |  |  |  |  |  | unless $1 == 2; | 
| 131 | 0 |  |  |  |  |  | pop(@path); | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 0 |  |  |  |  |  | $uri_obj->path(File::Spec::Unix->catdir(@path)); | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 |  |  |  |  |  | my $soap_url = $uri_obj->as_string; | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | # | 
| 139 |  |  |  |  |  |  | # remove any embedded credentials | 
| 140 |  |  |  |  |  |  | # | 
| 141 | 0 | 0 |  |  |  |  | $soap_url =~ s{\Q${userinfo}\E\@}{} if $userinfo; | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 0 |  |  |  |  |  | my $self = {}; | 
| 144 | 0 |  |  |  |  |  | bless $self, $class; | 
| 145 |  |  |  |  |  |  |  | 
| 146 | 0 |  |  |  |  |  | $self->url($soap_url); | 
| 147 | 0 |  |  |  |  |  | $self->user($user); | 
| 148 | 0 |  |  |  |  |  | $self->pass($pass); | 
| 149 | 0 |  |  |  |  |  | $self->debug($debug); | 
| 150 | 0 |  |  |  |  |  | $self->timeout($opt{timeout}); | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 |  |  |  |  |  | return $self | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | sub _check_for_errors { | 
| 156 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 157 | 0 |  |  |  |  |  | my $som = shift; | 
| 158 |  |  |  |  |  |  |  | 
| 159 | 0 | 0 |  |  |  |  | die "No response from server\n" | 
| 160 |  |  |  |  |  |  | unless $som; | 
| 161 |  |  |  |  |  |  |  | 
| 162 | 0 | 0 |  |  |  |  | die $som->fault->{ faultstring }."\n" if ($som->fault); | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 0 |  |  |  |  |  | my $result = $som->result; | 
| 165 | 0 |  |  |  |  |  | my @paramsout = $som->paramsout; | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 0 | 0 |  |  |  |  | warn YAML::Syck::Dump({result => $result, paramsout => \@paramsout}) | 
| 168 |  |  |  |  |  |  | if ($class->debug); | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 0 |  |  |  |  |  | my @results = ($result, @paramsout); | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 0 |  |  |  |  |  | foreach my $result (@results) { | 
| 173 | 0 | 0 |  |  |  |  | next unless Scalar::Util::reftype($result); | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | # | 
| 176 |  |  |  |  |  |  | # Look for Elluminate-specific errors | 
| 177 |  |  |  |  |  |  | # | 
| 178 | 0 | 0 | 0 |  |  |  | if ($result->{Code} | 
| 179 |  |  |  |  |  |  | && (my $code = $result->{Code}{Value})) { | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | # | 
| 182 |  |  |  |  |  |  | # Elluminate error! | 
| 183 |  |  |  |  |  |  | # | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 0 |  |  |  |  |  | my $reason = $result->{Reason}{Text}; | 
| 186 | 0 |  |  |  |  |  | my @stack_trace; | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 0 |  |  |  |  |  | my $stack = $result->{Detail}{Stack}; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 0 | 0 | 0 |  |  |  | if ($stack && (my $trace = $stack->{Trace})) { | 
| 191 | 0 | 0 |  |  |  |  | @stack_trace = (Elive::Util::_reftype($trace) eq 'ARRAY' | 
| 192 |  |  |  |  |  |  | ? @$trace | 
| 193 |  |  |  |  |  |  | : $trace); | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 0 |  |  |  |  |  | my %seen; | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 0 | 0 |  |  |  |  | my @error = grep {$_ && !$seen{$_}++} ($code, $reason, @stack_trace); | 
|  | 0 |  |  |  |  |  |  | 
| 200 | 0 | 0 |  |  |  |  | my $msg = @error ? join(' ', @error) : YAML::Syck::Dump($result); | 
| 201 | 0 |  |  |  |  |  | die "$msg\n"; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | =head2 check_command | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | my $command1 = Elive->check_command([qw{getUser listUser}]) | 
| 209 |  |  |  |  |  |  | my $command2 = Elive->check_command(deleteUser => 'd') | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | Find the first known command in the list. Raise an error if it's unknown; | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | See also: elive_lint_config. | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | =cut | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | sub check_command { | 
| 218 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 219 | 0 |  |  |  |  |  | my $commands = shift; | 
| 220 | 0 |  |  |  |  |  | my $crud = shift; #create, read, update or delete | 
| 221 | 0 |  |  |  |  |  | my $params = shift; | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 0 | 0 |  |  |  |  | if (Elive::Util::_reftype($commands) eq 'CODE') { | 
| 224 | 0 |  |  |  |  |  | $commands = $commands->($crud, $params); | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  |  | 
| 227 | 0 | 0 |  |  |  |  | $commands = [$commands] | 
| 228 |  |  |  |  |  |  | unless Elive::Util::_reftype($commands) eq 'ARRAY'; | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 0 |  |  |  |  |  | my $usage = "usage: \$class->check_command(\$name[,'c'|'r'|'u'|'d'])"; | 
| 231 | 0 | 0 | 0 |  |  |  | die $usage unless @$commands && $commands->[0]; | 
| 232 |  |  |  |  |  |  |  | 
| 233 | 0 |  |  |  |  |  | my $known_commands = $class->known_commands; | 
| 234 |  |  |  |  |  |  |  | 
| 235 | 0 |  |  |  |  |  | die "no known commands for class: $class" | 
| 236 | 0 | 0 | 0 |  |  |  | unless $known_commands && (keys %{$known_commands}); | 
| 237 |  |  |  |  |  |  |  | 
| 238 | 0 |  |  |  |  |  | my ($command) = grep {exists $known_commands->{$_}} @$commands; | 
|  | 0 |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 0 | 0 |  |  |  |  | croak "Unknown command(s): @{$commands}" | 
|  | 0 |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | unless $command; | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 0 | 0 |  |  |  |  | if ($crud) { | 
| 244 | 0 |  |  |  |  |  | $crud = lc(substr($crud,0,1)); | 
| 245 | 0 | 0 |  |  |  |  | die $usage | 
| 246 |  |  |  |  |  |  | unless $crud =~ m{^[c|r|u|d]$}xi; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 0 |  |  |  |  |  | my $command_type = $known_commands->{$command}; | 
| 249 | 0 | 0 | 0 |  |  |  | die "misconfigured command: $command" | 
| 250 |  |  |  |  |  |  | unless $command_type &&  $command_type  =~ m{^[c|r|u|d]+$}xi; | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 0 | 0 |  |  |  |  | die "command $command. Type mismatch. Expected $crud, found $command_type" | 
| 253 |  |  |  |  |  |  | unless ($crud =~ m{[$command_type]}i); | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 | 0 |  |  |  |  |  | return $command; | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =head2 known_commands | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | Returns an array of hash-value pairs for all Elluminate I commands | 
| 262 |  |  |  |  |  |  | required by Elive. This list is cross-checked by the script elive_lint_config. | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | =cut | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | =head2 call | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | my $som = $self->call( $cmd, %params ); | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | Performs an Elluminate SOAP method call. Returns the response as a | 
| 271 |  |  |  |  |  |  | SOAP::SOM object. | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | =cut | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | sub call { | 
| 276 | 0 |  |  | 0 |  |  | my ($self, $cmd, %params) = @_; | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 0 |  |  |  |  |  | $cmd = $self->check_command($cmd, undef, \%params); | 
| 279 |  |  |  |  |  |  |  | 
| 280 | 0 |  |  |  |  |  | my @soap_params = $self->_preamble($cmd); | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 0 |  |  |  |  |  | foreach my $name (keys %params) { | 
| 283 |  |  |  |  |  |  |  | 
| 284 | 0 |  |  |  |  |  | my $value = $params{$name}; | 
| 285 |  |  |  |  |  |  |  | 
| 286 | 0 |  |  | 0 |  |  | $value = SOAP::Data->type(string => Elive::Util::string($value)) | 
| 287 |  |  |  |  |  |  | unless (Scalar::Util::blessed($value) | 
| 288 | 0 | 0 | 0 |  |  |  | && try {$value->isa('SOAP::Data')}); | 
| 289 |  |  |  |  |  |  |  | 
| 290 | 0 |  |  |  |  |  | my $soap_param = $value->name($name); | 
| 291 |  |  |  |  |  |  |  | 
| 292 | 0 |  |  |  |  |  | push (@soap_params, $soap_param); | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  |  | 
| 295 | 0 |  |  |  |  |  | my $som = $self->soap->call( @soap_params ); | 
| 296 |  |  |  |  |  |  |  | 
| 297 | 0 |  |  |  |  |  | return $som; | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | =head2 disconnect | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | Closes a connection. | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | =cut | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | sub disconnect { | 
| 307 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 308 | 0 |  |  |  |  |  | return; | 
| 309 |  |  |  |  |  |  | } | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | =head2 url | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | my $url1 = $connection1->url; | 
| 314 |  |  |  |  |  |  | my $url2 = $connection2->url; | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | Returns a restful url for the connection. | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | =cut | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | sub DESTROY { | 
| 321 | 0 |  |  | 0 |  |  | shift->disconnect; | 
| 322 | 0 |  |  |  |  |  | return; | 
| 323 |  |  |  |  |  |  | } | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | L L L | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | =cut | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | 1; |