File Coverage

blib/lib/RDF/Sesame/Connection.pm
Criterion Covered Total %
statement 21 96 21.8
branch 0 38 0.0
condition 0 11 0.0
subroutine 7 12 58.3
pod 4 5 80.0
total 32 162 19.7


line stmt bran cond sub pod time code
1             # vim modeline vim600: set foldmethod=marker :
2             package RDF::Sesame::Connection;
3              
4 8     8   47 use strict;
  8         32  
  8         255  
5 8     8   40 use warnings;
  8         17  
  8         194  
6              
7 8     8   40 use Carp;
  8         13  
  8         777  
8 8     8   9764 use LWP::UserAgent;
  8         483678  
  8         449  
9 8     8   8958 use Time::HiRes qw( gettimeofday tv_interval );
  8         17025  
  8         43  
10              
11 8     8   1839 use RDF::Sesame;
  8         16  
  8         236  
12 8     8   6163 use RDF::Sesame::Repository;
  8         21  
  8         9737  
13              
14             our $VERSION = '0.17';
15              
16             =head1 NAME
17              
18             RDF::Sesame::Connection - A connection to a Sesame server
19              
20             =head1 DESCRIPTION
21              
22             This class represents a connection to a specific Sesame server and provides
23             methods which are generally useful for interacting with a Sesame server.
24             This class is predominantly used to create an RDF::Sesame::Repository object
25             which is in turn used to execute queries, remove triples, upload triples, etc.
26              
27             =head1 METHODS
28              
29             =head2 open ( %opts )
30              
31             Creates an RDF::Sesame::Repository object. There is no communication with
32             the Sesame server during this call, so it should be very fast. The C<%opts>
33             parameter provides a series of named options to use when creating the Repository
34             object. A list of options which are currently understood is provided below.
35             If a single scalar is given instead of C<%opts>, the scalar will be treated
36             as the value of the 'id' option.
37              
38             =head3 id
39              
40             The ID of the repository that you want to open. The method will return
41             successfully even if there is no repository on the server with the
42             repository ID provided. However, subsequent attempts to do anything
43             useful with the Repository object will fail.
44              
45             If you're not sure what the valid repository IDs are, use the
46             C method documented below. If no repository ID is given,
47             the method returns the empty string.
48              
49             =head3 query_language
50              
51             This is equivalent to calling query_language() on the newly created Repository
52             object. See that documentation for further information.
53              
54             =head3 strip
55              
56             This option serves as a default for the strip option to
57             RDF::Sesame::Repository::select Typically, that method defaults to 'none'
58             however specifying this option when opening the repository changes the default.
59              
60             =cut
61              
62             sub open {
63 0     0 1   RDF::Sesame::Repository->new(@_);
64             }
65              
66             =head2 repositories ( [ $refresh ] )
67              
68             When called in list context, returns a list of the IDs of the repositories
69             which are available through this connection. The response in scalar context
70             is reserved for future use. So don't call repositories() in scalar
71             context yet (currently, it returns C).
72              
73             Only the first call to repositories() communicates with the server.
74             Subsequent calls return a cached copy of the results from the first
75             communication. If you want to get new results directly from the server,
76             pass a true value for the $refresh parameter. These new results will replace
77             the previously cached results.
78              
79             =cut
80              
81             sub repositories {
82 0     0 1   my $self = shift;
83              
84 0 0         return undef unless wantarray;
85              
86             # check the repositories cache
87 0 0 0       if( $self->{repos} && !$_[0] ) {
88 0           return @{ $self->{repos} };
  0            
89             }
90              
91             # call listRepositories on the server
92 0           my $r = $self->command(undef, 'listRepositories');
93 0           my @repos;
94 0 0         if( $r->success ) {
95 0           foreach ( @{ $r->parsed_xml->{repository} } ) {
  0            
96 0           push(@repos, $_->{id});
97             }
98              
99 0           $self->{repos} = \@repos;
100             } else {
101             # there was an error, so no repositories are available
102 0           return ();
103             }
104              
105 0           return @repos;
106             }
107              
108             =head2 disconnect
109              
110             Logs out of the Sesame server and closes any connections to the server.
111             Attempts to use this object or any of the RDF::Sesame::Repository objects
112             created by this object after it has been disconnected will result in ugly
113             problems.
114              
115             This method is B called when an RDF::Sesame::Connection object is
116             destroyed. If you want to explicitly logout of the server, you must
117             call this method.
118              
119             Returns true upon success, false otherwise.
120              
121             =cut
122              
123             sub disconnect {
124 0     0 1   my $self = shift;
125              
126 0           my $resp = 0;
127 0 0         if( $self->{authed} ) {
128 0           $resp = $self->command(undef, 'logout')->success;
129             } else {
130 0           $resp = 1; # we can't fail if we're already logged out
131             }
132              
133 0 0         if( $resp ) {
134 0           $self->{authed} = 0;
135 0           delete $self->{ua};
136             }
137              
138 0           return $resp;
139             }
140              
141             =head1 INTERNAL METHODS
142              
143             These methods might be useful to some users, so they're documented, but
144             most will never need them.
145              
146             =head2 command ( $repository_id, $name [, $parameters ] )
147              
148             This method executes a command against a repository
149             using the parameters provided in the hashref. The intended way to execute
150             commands against the server is to use an RDF::Sesame::Repository object.
151              
152             The C<$repository_id> parameter is just a shortcut for adding a I
153             parameter in the call to the server. If you pass C as the repository
154             ID, the method should still work fine and no I parameter will
155             be passed to the server.
156              
157             The result of this method is an RDF::Sesame::Response object. That object
158             can be used to determine whether the command succeeded or failed.
159              
160             Parameters :
161             $repository_id The ID of the repository you want to execute
162             the query against.
163              
164             $name The name of the command to execute.
165              
166             $parameters An optional hashref containing the parameter
167             names and values to pass to the server when executing
168             the command.
169            
170             Return :
171             RDF::Sesame::Response
172              
173             =cut
174              
175             # TODO use XML::SAX instead of XML::Simple (details follow)
176             # The basic implementation might be something like this
177             #
178             # my ($self, $cmd) = @_;
179             # my $handler_class = $handlers{$cmd};
180             # my $handler = $handler_class->new();
181             # $parser = XML::SAX::ParserFactory( Handler => $handler );
182             # $self->{ua}->post(
183             # ...,
184             # ':content_cb' => sub { $parser->parse_string(...) }
185             # );
186             # return $handler->response();
187             #
188             # I should be able to implement the above if I make the new response
189             # objects implement the current Response interface. Once that works,
190             # I can change the way the old code uses the response objects
191             # (if that's still necessary).
192              
193             { my $count = 0;
194             sub command {
195 0     0 1   my $self = shift;
196              
197             # make sure we have a hash
198 0           my $params;
199 0 0         if( ref($_[2]) eq 'HASH' ) {
200 0           $params = $_[2];
201             } else {
202 0           $params = {};
203             }
204              
205             # add the repository name to the hash
206 0 0         $params->{repository} = $_[0] if defined $_[0];
207              
208 0           my $cmd = $_[1];
209              
210             # make the request. Either GET or POST depending on the command
211 0           my $cmd_uri = $self->{server} . $cmd;
212 0           my $r; # the server's HTTP::Response
213 0           my $content_cb = delete $params->{':content_cb'};
214 0           my $start = [ gettimeofday() ];
215 0 0 0       if( $cmd eq 'listRepositories' or $cmd eq 'logout' ) {
216             # send a request using HTTP-GET
217 0           $r = $self->{ua}->get( $cmd_uri, %$params );
218             } else {
219             # send a request using HTTP-POST ('multipart/form-data' encoded)
220 0 0         $r = $self->{ua}->post(
221             $cmd_uri,
222             {}, # empty form since the real stuff is in 'Content'
223             Content_Type => 'form-data',
224             Content => $params,
225             ( $content_cb ? (':content_cb' => $content_cb) : () )
226             );
227             }
228              
229             # make an RDF::Sesame::Response object for return
230 0           my $response = RDF::Sesame::Response->new($r);
231              
232 0 0         if ( $ENV{RDFSESAME_DEBUG} ) {
233 0           my $elapsed = int( 1000 * tv_interval($start) ); # in milliseconds
234 0           printf STDERR "Command %d : Ran $cmd in $elapsed ms\n", $count++;
235             }
236              
237 0           return $response;
238             }
239             }
240              
241             # This method should really only be called by
242             # RDF::Sesame::connect and it's documented there, so
243             # there's no need to document it here also.
244              
245             sub new {
246 0     0 0   my $class = shift;
247              
248             # Establish the defaults for each option
249 0           my %defaults = (
250             host => 'localhost',
251             port => 80,
252             directory => 'sesame',
253             timeout => 10,
254             );
255              
256 0           my %opts;
257 0 0         if( @_ == 1 ) {
258 0           $opts{host} = shift;
259             } else {
260 0           %opts = @_;
261             }
262              
263 0 0 0       if( $opts{host} and $opts{host} =~/^(.*):(\d+)$/ ) {
    0          
264 0           $opts{host} = $1;
265 0           $opts{port} = $2;
266             } elsif( $opts{uri} ) {
267 0           require URI;
268 0           my $uri = URI->new( $opts{uri} );
269              
270             # set the individual options based on the URI
271 0           $opts{host} = $uri->host;
272 0           $opts{port} = $uri->port;
273 0           $opts{directory} = $uri->path;
274              
275 0   0       my($user, $pass) = split(/:/, $uri->userinfo || '', 2);
276 0 0         $opts{username} = $user if defined $user;
277 0 0         $opts{password} = $pass if defined $pass;
278             }
279              
280             # set the defaults
281 0           while( my ($k,$v) = each %defaults ) {
282 0 0         $opts{$k} = $v unless exists $opts{$k};
283             }
284              
285             # normalize the sesame directory
286 0           $opts{directory} =~ s#^/+##g;
287 0           $opts{directory} =~ s#/+$##g;
288              
289             # create a user agent for making HTTP requests
290 0           my $ua = LWP::UserAgent->new(
291             agent => "rdf-sesame/$RDF::Sesame::VERSION ",
292             keep_alive => 1,
293             cookie_jar => {},
294             timeout => $opts{timeout},
295             );
296              
297             # create our new self
298 0           my $self = bless {
299             server => "http://$opts{host}:$opts{port}/$opts{directory}/servlets/",
300             ua => $ua,
301             authed => 0, # are we logged in?
302             repos => undef, # list of available repositories
303             }, $class;
304              
305             # do we even need to login ?
306 0 0         return $self unless defined $opts{username};
307              
308             # yup, so go ahead and do it
309 0           my $r;
310 0 0         $opts{password} = '' unless defined $opts{password};
311 0           $r = $self->command(
312             undef,
313             'login',
314             {user=>$opts{username}, password=>$opts{password}}
315             );
316              
317 0 0         unless( $r->success ) {
318 0           $RDF::Sesame::errstr = $r->errstr;
319 0           return '';
320             }
321              
322 0           $self->{authed} = 1;
323 0           return $self;
324             }
325              
326             =head1 AUTHOR
327              
328             Michael Hendricks
329              
330             =cut
331              
332             return 1;