File Coverage

blib/lib/RDF/Query/Client.pm
Criterion Covered Total %
statement 18 20 90.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 25 27 92.5


line stmt bran cond sub pod time code
1 1     1   10296915 use 5.010;
  1         6  
  1         70  
2 1     1   10 use strict;
  1         3  
  1         103  
3 1     1   8 use warnings;
  1         9  
  1         151  
4              
5             package RDF::Query::Client;
6              
7             BEGIN {
8 1     1   2 $RDF::Query::Client::AUTHORITY = 'cpan:TOBYINK';
9 1         28 $RDF::Query::Client::VERSION = '0.114';
10             }
11              
12 1     1   7 use Carp 0 qw/carp/;
  1         33  
  1         82  
13 1     1   16 use LWP::UserAgent 0 qw//;
  1         22  
  1         35  
14 1     1   644 use RDF::Trine 0.133 qw//;
  0            
  0            
15             use Scalar::Util 0 qw/blessed/;
16             use URI::Escape 0 qw/uri_escape/;
17              
18             use namespace::clean;
19              
20             sub new
21             {
22             my $class = shift;
23             my ($query, $opts) = @_;
24            
25             bless {
26             query => $query ,
27             useragent => ($opts->{UserAgent} // undef) ,
28             results => [] ,
29             error => undef ,
30             }, $class;
31             }
32              
33             sub execute
34             {
35             my $self = shift;
36             my ($endpoint, $opts) = @_;
37            
38             my $ua = $opts->{UserAgent} // $self->useragent;
39             my $request = $self->_prepare_request($endpoint, $opts);
40             my $response = $ua->request($request);
41            
42             push @{ $self->{results} }, { response => $response };
43            
44             my $iterator = $self->_create_iterator($response);
45             return unless defined $iterator;
46             $self->{results}[-1]{iterator} = $iterator;
47            
48             wantarray ? $iterator->get_all : $iterator;
49             }
50              
51             our $LRDD;
52             sub discover_execute
53             {
54             my $self = shift;
55             my ($resource_uri, $opts) = @_;
56            
57             unless ($LRDD)
58             {
59             no warnings;
60             eval 'use HTTP::LRDD;';
61             eval
62             {
63             $LRDD = HTTP::LRDD->new('http://ontologi.es/sparql#endpoint', 'http://ontologi.es/sparql#fingerpoint')
64             if HTTP::LRDD->can('new');
65             };
66             }
67            
68             unless (blessed($LRDD) and $LRDD->isa('HTTP::LRDD'))
69             {
70             $self->{error} = "Need HTTP::LRDD to use the discover_execute feature.";
71             return;
72             }
73            
74             my $endpoint = $LRDD->discover($resource_uri)
75             or return;
76            
77             return $self->execute($endpoint, $opts);
78             }
79              
80             sub get
81             {
82             my $self = shift;
83             my $stream = $self->execute(@_);
84            
85             if (ref $stream)
86             {
87             if ($stream->is_bindings)
88             {
89             my $row = $stream->next;
90             return $stream->binding_values;
91             }
92             if ($stream->is_graph)
93             {
94             my $st = $stream->next;
95             return ($st->subject, $st->predicate, $st->object);
96             }
97             if ($stream->is_boolean)
98             {
99             my @rv;
100             push @rv, 1 if $stream->get_boolean;
101             return @rv;
102             }
103             }
104            
105             return;
106             }
107              
108             sub as_sparql
109             {
110             return (shift)->{query};
111             }
112              
113             sub http_response
114             {
115             return (shift)->{results}[-1]{response};
116             }
117              
118             sub error
119             {
120             return (shift)->{error};
121             }
122              
123             sub prepare { carp "Method not implemented"; }
124             sub execute_plan { carp "Method not implemented"; }
125             sub execute_with_named_graphs { carp "Method not implemented"; }
126             sub aggregate { carp "Method not implemented"; }
127             sub pattern { carp "Method not implemented"; }
128             sub sse { carp "Method not implemented"; }
129             sub algebra_fixup { carp "Method not implemented"; }
130             sub add_function { carp "Method not implemented"; }
131             sub supported_extensions { carp "Method not implemented"; }
132             sub supported_functions { carp "Method not implemented"; }
133             sub add_computed_statement_generator { carp "Method not implemented"; }
134             sub get_computed_statement_generators { carp "Method not implemented"; }
135             sub net_filter_function { carp "Method not implemented"; }
136             sub add_hook_once { carp "Method not implemented"; }
137             sub add_hook { carp "Method not implemented"; }
138             sub parsed { carp "Method not implemented"; }
139             sub bridge { carp "Method not implemented"; }
140             sub log { carp "Method not implemented"; }
141             sub logger { carp "Method not implemented"; }
142             sub costmodel { carp "Method not implemented"; }
143              
144             sub useragent
145             {
146             my $self = shift;
147            
148             unless (defined $self->{useragent})
149             {
150             my $accept = join q{, } => (
151             'application/sparql-results+xml',
152             'application/sparql-results+json;q=0.9',
153             'application/rdf+xml',
154             'application/x-turtle',
155             'text/turtle',
156             );
157             my $agent = sprintf(
158             '%s/%s (%s) ',
159             __PACKAGE__,
160             __PACKAGE__->VERSION,
161             do { no strict "refs"; ${ref($self)."::AUTHORITY"} },
162             );
163             $self->{useragent} = LWP::UserAgent->new(
164             agent => $agent,
165             max_redirect => 2,
166             parse_head => 0,
167             protocols_allowed => [qw/http https/],
168             );
169             $self->{useragent}->default_header(Accept => $accept);
170             }
171            
172             $self->{useragent};
173             }
174              
175             sub _prepare_request
176             {
177             my $self = shift;
178             my ($endpoint, $opts) = @_;
179            
180             my $method = uc($opts->{QueryMethod} // '');
181             if ($method !~ /^(get|post|patch)$/i)
182             {
183             $method = (length $self->{'query'} > 511) ? 'POST' : 'GET';
184             }
185            
186             my $param = $opts->{QueryParameter} // 'query';
187            
188             my $uri = '';
189             my $cnt = '';
190             if ($method eq 'GET')
191             {
192             $uri = $endpoint . ($endpoint =~ /\?/ ? '&' : '?');
193             $uri .= sprintf(
194             "%s=%s",
195             uri_escape($param),
196             uri_escape($self->{query})
197             );
198             if ($opts->{Parameters})
199             {
200             foreach my $field (keys %{$opts->{Parameters}})
201             {
202             $uri .= sprintf(
203             "&%s=%s",
204             uri_escape($field),
205             uri_escape($opts->{Parameters}->{$field}),
206             );
207             }
208             }
209             }
210             elsif ($method eq 'POST')
211             {
212             $uri = $endpoint;
213             $cnt = sprintf(
214             "%s=%s",
215             uri_escape($param),
216             uri_escape($self->{query})
217             );
218             if ($opts->{Parameters})
219             {
220             foreach my $field (keys %{$opts->{Parameters}})
221             {
222             $cnt .= sprintf(
223             "&%s=%s",
224             uri_escape($field),
225             uri_escape($opts->{Parameters}{$field}),
226             );
227             }
228             }
229             }
230            
231             my $req = HTTP::Request->new($method => $uri);
232            
233             my $type = $opts->{ContentType} // '';
234             if ($type =~ m{^application/sparql-query}i)
235             {
236             $req->content_type('application/sparql-query');
237             $req->content($self->{query});
238             }
239             elsif ($type =~ m{^application/sparql-update}i)
240             {
241             $req->content_type('application/sparql-update');
242             $req->content($self->{query});
243             }
244             else
245             {
246             $req->content_type('application/x-www-form-urlencoded');
247             $req->content($cnt);
248             }
249            
250             $req->authorization_basic($opts->{AuthUsername}, $opts->{AuthPassword})
251             if defined $opts->{AuthUsername};
252            
253             foreach my $k (keys %{$opts->{Headers}})
254             {
255             $req->header($k => $opts->{Headers}{$k});
256             }
257            
258             $req;
259             }
260              
261             sub _create_iterator
262             {
263             my $self = shift;
264             my ($response) = @_;
265            
266             unless ($response->is_success)
267             {
268             $self->{error} = $response->message;
269             return;
270             }
271            
272             if ($response->content_type =~ /sparql.results/)
273             {
274             local $@ = undef;
275             my $iterator = eval
276             {
277             if ($response->content_type =~ /json/)
278             { RDF::Trine::Iterator->from_json($response->decoded_content); }
279             else
280             { RDF::Trine::Iterator->from_string($response->decoded_content); }
281             };
282             return $iterator
283             if $iterator;
284            
285             $self->{error} = $@;
286             return;
287             }
288             else
289             {
290             my $model;
291             eval
292             {
293             my $parser = RDF::Trine::Parser->parser_by_media_type($response->content_type);
294             my $tmp = RDF::Trine::Model->temporary_model;
295             $parser->parse_into_model($response->base, $response->decoded_content, $tmp);
296             $model = $tmp;
297             };
298            
299             return $model->as_stream if defined $model;
300            
301             $self->{error} = sprintf("Response of type '%s' could not be parsed.", $response->content_type);
302             return;
303             }
304             }
305              
306             1;
307              
308             __END__
309              
310             =pod
311              
312             =encoding utf8
313              
314             =begin stopwords
315              
316             'sparql'
317             application/sparql-query
318             application/sparql-update
319             application/x-www-form-urlencoded
320             rel
321             WebID
322              
323             =end stopwords
324              
325             =head1 NAME
326              
327             RDF::Query::Client - get data from W3C SPARQL Protocol 1.0 servers
328              
329             =head1 SYNOPSIS
330              
331             use RDF::Query::Client;
332            
333             my $query = RDF::Query::Client
334             ->new('SELECT DISTINCT ?s WHERE { ?s ?p ?o . }');
335            
336             my $iterator = $query->execute('http://example.com/sparql');
337            
338             while (my $row = $iterator->next) {
339             print $row->{s}->as_string;
340             }
341              
342             =head1 DESCRIPTION
343              
344             =head2 Constructor
345              
346             =over 4
347              
348             =item C<< new ( $sparql, \%opts ) >>
349              
350             Returns a new RDF::Query::Client object for the specified C<$sparql>.
351             The object's interface is designed to be roughly compatible with RDF::Query
352             objects, though RDF::Query is not required by this module.
353              
354             Options include:
355              
356             =over 4
357              
358             =item B<UserAgent> - an LWP::UserAgent to handle HTTP requests.
359              
360             =back
361              
362             Unlike RDF::Query, where you get a choice of query language, the query
363             language for RDF::Query::Client is always 'sparql'. RDF::TrineShortcuts offers
364             a way to perform RDQL queries on remote SPARQL stores though (by transforming
365             RDQL to SPARQL).
366              
367             =back
368              
369             =head2 Public Methods
370              
371             =over 4
372              
373             =item C<< execute ( $endpoint, \%opts ) >>
374              
375             C<$endpoint> is a URI object or string containing the endpoint
376             URI to be queried.
377              
378             Options include:
379              
380             =over 4
381              
382             =item * B<UserAgent> - an LWP::UserAgent to handle HTTP requests.
383              
384             =item * B<QueryMethod> - 'GET', 'POST', 'PATCH' or undef (automatic).
385              
386             =item * B<QueryParameter> - defaults to 'query'.
387              
388             =item * B<AuthUsername> - HTTP Basic authorization.
389              
390             =item * B<AuthPassword> - HTTP Basic authorization.
391              
392             =item * B<Headers> - additional headers to include (hashref).
393              
394             =item * B<Parameters> - additional GET/POST fields to include (hashref).
395              
396             =item * B<ContentType> - 'application/sparql-query',
397             'application/sparql-update' or 'application/x-www-form-urlencoded' (default).
398              
399             =back
400              
401             Returns undef on error; an RDF::Trine::Iterator if called in a
402             scalar context; an array obtained by calling C<get_all> on the
403             iterator if called in list context.
404              
405             =item C<< discover_execute( $resource_uri, \%opts ) >>
406              
407             Experimental feature. Discovers a SPARQL endpoint relevant to $resource_uri
408             and then calls C<< $query->execute >> against that. Uses an LRDD-like
409             method to discover the endpoint. If you're publishing data and want people
410             to be able to find your SPARQL endpoint automatically, the easiest way is to
411             include an Link header in HTTP responses:
412              
413             Link: </my/endpoint>; rel="http://ontologi.es/sparql#endpoint"
414              
415             Change the URL in the angled brackets, but not the URL in the rel string.
416              
417             This feature requires the HTTP::LRDD package to be installed.
418              
419             =item C<< get ( $endpoint, \%opts ) >>
420              
421             Executes the query using the specified endpoint, and returns the first
422             matching row as a LIST of values. Takes the same arguments as C<execute>.
423              
424             =item C<< as_sparql >>
425              
426             Returns the query as a string in the SPARQL syntax.
427              
428             =item C<< useragent >>
429              
430             Returns the LWP::UserAgent object used for retrieving web content.
431              
432             =item C<< http_response >>
433              
434             Returns the last HTTP Response the client experienced.
435              
436             =item C<< error >>
437              
438             Returns the last error the client experienced.
439              
440             =back
441              
442             =head2 Security
443              
444             The C<execute> and C<get> methods allow AuthUsername and
445             AuthPassword options to be passed to them for HTTP Basic authentication.
446             For more complicated authentication (Digest, OAuth, Windows, etc),
447             it is also possible to pass these methods a customised LWP::UserAgent.
448              
449             If you have the Crypt::SSLeay package installed, requests to HTTPS
450             endpoints should work. It's possible to specify a client X.509
451             certificate (e.g. for WebID authentication) by setting particular
452             environment variables. See L<Crypt::SSLeay> documentation for details.
453              
454             =head1 BUGS
455              
456             Probably.
457              
458             Please report any you find here:
459             L<https://rt.cpan.org/Dist/Display.html?Queue=RDF-Query-Client>.
460              
461             =head1 SEE ALSO
462              
463             =over 4
464              
465             =item * L<RDF::Trine>, L<RDF::Trine::Iterator>
466              
467             =item * L<RDF::Query>
468              
469             =item * L<LWP::UserAgent>
470              
471             =item * L<http://www.w3.org/TR/rdf-sparql-protocol/>
472              
473             =item * L<http://www.w3.org/TR/rdf-sparql-query/>
474              
475             =item * L<http://www.perlrdf.org/>
476              
477             =back
478              
479             =head1 AUTHOR
480              
481             Toby Inkster, E<lt>tobyink@cpan.orgE<gt>
482              
483             =head1 COPYRIGHT AND LICENSE
484              
485             Copyright (C) 2009-2013 by Toby Inkster
486              
487             This library is free software; you can redistribute it and/or modify
488             it under the same terms as Perl itself.
489              
490             =head1 DISCLAIMER OF WARRANTIES
491              
492             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
493             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
494             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
495              
496             =cut
497