File Coverage

blib/lib/RDF/Query/Client.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


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