File Coverage

blib/lib/RDF/AllegroGraph/Repository4.pm
Criterion Covered Total %
statement 45 322 13.9
branch 0 134 0.0
condition 0 33 0.0
subroutine 15 45 33.3
pod 25 26 96.1
total 85 560 15.1


line stmt bran cond sub pod time code
1             package RDF::AllegroGraph::Repository4;
2              
3 15     15   84 use strict;
  15         217  
  15         503  
4 15     15   79 use warnings;
  15         28  
  15         455  
5              
6 15     15   77 use base qw(RDF::AllegroGraph::Repository);
  15         30  
  15         2265  
7              
8 15     15   84 use Data::Dumper;
  15         36  
  15         969  
9 15     15   88 use feature "switch";
  15         23  
  15         1368  
10              
11 15     15   77 use JSON;
  15         50  
  15         100  
12 15     15   1839 use URI::Escape qw/uri_escape_utf8/;
  15         30  
  15         1048  
13              
14 15     15   2038 use HTTP::Request::Common;
  15         4618  
  15         10800  
15              
16             =pod
17              
18             =head1 NAME
19              
20             RDF::AllegroGraph::Repository4 - AllegroGraph repository handle for AGv4
21              
22             =head1 INTERFACE
23              
24             Same as L from which we inherit.
25              
26             =cut
27              
28             sub new {
29 0     0 0   my $class = shift;
30 0           my %options = @_;
31 0           my $self = bless \%options, $class;
32 0 0         $self->{path} = $self->{CATALOG}->{SERVER}->{ADDRESS} . ($self->{CATALOG}->{NAME} eq '/'
33             ? ''
34             : '/catalogs' . $self->{CATALOG}->{NAME} ) . '/repositories/' . $self->{id};
35 0           return $self;
36             }
37              
38             =pod
39              
40             =over
41              
42             =item B
43              
44             This read-only accessor method returns the id of the repository.
45              
46             =cut
47              
48             sub id {
49 0     0 1   my $self = shift;
50 0 0         return $self->{CATALOG}->{NAME} eq '/'
51             ? '/' . $self->{id}
52             : $self->{CATALOG}->{NAME} . '/' . $self->{id};
53             }
54              
55             =pod
56              
57             =item B
58              
59             I<$repo>->disband
60              
61             This method removes the repository from the server. The object cannot be used after that, obviously.
62              
63             =cut
64              
65             sub disband {
66 0     0 1   my $self = shift;
67 0           my $requ = HTTP::Request->new (DELETE => $self->{path});
68 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ);
69 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
70             }
71              
72             =pod
73              
74             =item B
75              
76             I<$nr_triples> = I<$repo>->size
77              
78             Returns the size of the repository in terms of the number of triples.
79              
80             B: As of time of writing, AllegroGraph counts duplicate triples!
81              
82             =cut
83              
84             sub size {
85 0     0 1   my $self = shift;
86 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '/size');
87 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
88 0           return $resp->content;
89             }
90              
91             =pod
92              
93             =back
94              
95             =head2 Methods (over those we inherit)
96              
97             =over
98              
99             =item B (since v0.06)
100              
101             I<$session> = I<$repo>->session
102              
103             This method forks a session out of the current repository session. Unlike a transaction, all changes
104             are autocommitted into the mother repository. But AG4 needs a separate connection thread for some
105             specific features (SNA, loading Prolog knowledge, etc.)
106              
107             =cut
108              
109             sub session {
110 0     0 1   my $self = shift;
111 0           my %opts = @_;
112 0   0       $opts{autoCommit} ||= 'true'; # this is - by default - a session, so whatever we do, it will shine through
113 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->post ($self->{path} . '/session', %opts);
114 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
115 0           my $url = $resp->content;
116 0           $url =~ s/^\"//; $url =~ s/\"$//; # for some very odd reason we get this URI in the content
  0            
117             { # try to find authentication information inside the repo URL
118 0 0         if ($self->{path} =~ m{http://(.+?@)}) {
  0            
119 0           my $auth = $1;
120 0           $url =~ s{http://}{http://$auth};
121             }
122             }
123 0 0         if ($opts{autoCommit} eq 'true') { # we do like 'true' strings ...
124 15     15   18155 use RDF::AllegroGraph::Session4;
  15         38  
  15         1158  
125 0           return new RDF::AllegroGraph::Session4 (path => $url, # the newly returned URL will be its home
126             CATALOG => $self->{CATALOG}); # and the catalog will be the same
127             } else {
128 15     15   15799 use RDF::AllegroGraph::Transaction4;
  15         43  
  15         5670  
129 0           return new RDF::AllegroGraph::Transaction4 (path => $url, # the newly returned URL will be its home
130             CATALOG => $self->{CATALOG}); # and the catalog will be the same
131             }
132             }
133              
134             =pod
135              
136             =item B (since v0.06)
137              
138             I<$tx> = I<$repo>->transaction
139              
140             This method forks a transaction out of the current repository session. That transaction is itself a
141             repository session (and a session, for that matter). Whatever you do in the transaction, will stay
142             in the transaction. With calling the C method (see L),
143             you will simply empty the transaction. That is also the default behaviour, if the transaction object
144             goes out of scope.
145              
146             To manifest any changes you will have to invoke C on the transaction object.
147              
148             =cut
149              
150             sub transaction {
151 0     0 1   my $self = shift;
152 0           return $self->session (autoCommit => 'false');
153             }
154              
155             =pod
156              
157             =item B (since v0.06)
158              
159             I<@blanks> = I<$repo>->blanks (I)
160              
161             This method asks the server to create a number of blank nodes in the repository. The ids of these
162             nodes will be returned. By default, one node will be created, but you can ask for more.
163              
164             =cut
165              
166             sub blanks {
167 0     0 1   my $self = shift;
168 0   0       my $amount = shift || 1;
169              
170 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->post ($self->{path} . '/blankNodes',
171             'Content-Type' => 'application/x-www-form-urlencoded',
172             'Accept' => 'application/json',
173             'Content' => { 'amount' => $amount });
174 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
175 0           return @{ from_json ($resp->content) };
  0            
176             }
177              
178             =pod
179              
180             =item B
181              
182             I<$repo>->add ('file://....', ...)
183              
184             I<$repo>->add ('http://....', ...)
185              
186             I<$repo>->add (' triples in N3 ', ...)
187              
188             I<$repo>->add ([ I<$subj_uri>, I<$pred_uri>, I<$obj_uri> ], ...)
189              
190             This method adds triples to the repository. The information can be provided in any of the following
191             ways (also mixed):
192              
193             =over
194              
195             =item file, HTTP, FTP URL
196              
197             If a string looks like an URL, it will be dereferenced, the contents of the resource consulted and
198             that shipped to the repository on the server. If the resource cannot be read, an exception C
199             not open> will be raised. Any number of these URLs can be provided as parameter.
200              
201             B: Only N3 files are supported, and also only when the URL ends with the extension C or
202             C.
203              
204             =item N3 triple string
205              
206             If the string looks like N3 notated triples, that content is shipped to the server.
207              
208             =item ARRAY reference
209              
210             The reference is interpreted as one triple (statement), containing 3 URIs. These will be shipped
211             as-is to the server.
212              
213             =back
214              
215             If the server chokes on any of the above, an exception C is raised.
216              
217             B: There are no precautions for over-large content. Yet.
218              
219             B: Named graphs (aka I) are not handled. Yet.
220              
221              
222             =cut
223              
224              
225             sub add {
226 0     0 1   _put_post_stmts ('POST', @_);
227             }
228              
229             sub _put_post_stmts {
230 0     0     my $method = shift;
231 0           my $self = shift;
232              
233 0           my @stmts; # collect triples there
234             my $n3; # collect N3 stuff there
235 0           my @files; # collect file names here
236 15     15   101 use Regexp::Common qw/URI/;
  15         30  
  15         156  
237              
238 0           foreach my $item (@_) { # walk through what we got
239 0 0         if (ref($item) eq 'ARRAY') { # a triple statement
    0          
    0          
    0          
    0          
240 0           push @stmts, $item;
241             } elsif (ref ($item)) {
242 0           die "don't know what to do with it";
243             } elsif ($item =~ /^$RE{URI}{HTTP}/) {
244 0           push @files, $item;
245             } elsif ($item =~ /^$RE{URI}{FTP}/) {
246 0           push @files, $item;
247             } elsif ($item =~ /^$RE{URI}{file}/) {
248 0           push @files, $item;
249             } else { # scalar => N3
250 0           $n3 .= $item;
251             }
252             }
253              
254 0           my $ua = $self->{CATALOG}->{SERVER}->{ua}; # local handle
255              
256 0 0         if (@stmts) { # if we have something to say to the server
257 0           given ($method) {
258 0           when ('POST') {
259 0           my $resp = $ua->post ($self->{path} . '/statements',
260             'Content-Type' => 'application/json', 'Content' => encode_json (\@stmts) );
261 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
262             }
263 0           when ('PUT') {
264 0           my $requ = HTTP::Request->new (PUT => $self->{path} . '/statements',
265             [ 'Content-Type' => 'application/json' ], encode_json (\@stmts));
266 0           my $resp = $ua->request ($requ);
267 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
268             }
269 0           when ('DELETE') { # DELETE
270             # first bulk delete facts, i.e. where there are no wildcards
271 0 0 0       my @facts = grep { defined $_->[0] && defined $_->[1] && defined $_->[2] } @stmts;
  0            
272 0           my $requ = HTTP::Request->new (POST => $self->{path} . '/statements/delete',
273             [ 'Content-Type' => 'application/json' ], encode_json (\@facts));
274 0           my $resp = $ua->request ($requ);
275 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
276              
277             # the delete one by one those with wildcards
278 0   0       my @wildcarded = grep { ! defined $_->[0] || ! defined $_->[1] || ! defined $_->[2] } @stmts;
  0            
279 0           foreach my $w (@wildcarded) {
280 0           my $requ = HTTP::Request->new (DELETE => _to_uri ($self->{path} . '/statements', $w, {}) );
281 0           my $resp = $ua->request ($requ);
282 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
283             }
284             }
285 0           default { die "You should never end here: Unhandled '$method'"; }
  0            
286             }
287             }
288 0 0         if ($n3) { # if we have something to say to the server
289 0           my $requ = HTTP::Request->new ($method => $self->{path} . '/statements', [ 'Content-Type' => 'text/plain' ], $n3);
290 0           my $resp = $ua->request ($requ);
291 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
292             }
293 0           for my $file (@files) { # if we have something to say to the server
294 15     15   19535 use LWP::Simple;
  15         34  
  15         162  
295 0 0         my $content = get ($file) or die "Could not open URL '$file'";
296 0           my $mime; # lets guess the mime type
297 0           given ($file) { # magic does not normally cope well with RDF/N3, so do it by extension
298 0           when (/\.n3$/) { $mime = 'text/plain'; } # well, not really, since its text/n3
  0            
299 0           when (/\.nt$/) { $mime = 'text/plain'; }
  0            
300 0           when (/\.xml$/) { $mime = 'application/rdf+xml'; }
  0            
301 0           when (/\.rdf$/) { $mime = 'application/rdf+xml'; }
  0            
302 0           default { die; }
  0            
303             }
304              
305 0           my $requ = HTTP::Request->new ($method => $self->{path} . '/statements', [ 'Content-Type' => $mime ], $content);
306 0           my $resp = $ua->request ($requ);
307 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
308              
309 0           $method = 'POST'; # whatever the first was, the others must add to it!
310             }
311              
312              
313             }
314              
315             sub _to_uri {
316 0     0     my $path = shift;
317 0           my $w = shift;
318 0           my $options = shift;
319              
320 0           my $url = new URI ($path);
321 0 0         $url->query_form ((defined $w->[0]
    0          
    0          
    0          
    0          
322             ? ('subj' => $w->[0])
323             : () ),
324             (defined $w->[1]
325             ? ('pred' => $w->[1])
326             : () ),
327             (ref ($w->[2]) eq 'ARRAY'
328             ? ('obj' => $w->[2]->[0],
329             'objEnd' => $w->[2]->[1]
330             )
331             : (defined $w->[2]
332             ? ('obj' => $w->[2])
333             : ()
334             )),
335             (defined $options->{limit}
336             ? (limit => $options->{limit})
337             : ())
338             );
339 0           return $url;
340             }
341              
342             =pod
343              
344             =item B
345              
346             This method behaves exactly like C, except that any existing content in the repository is wiped
347             before adding anything.
348              
349             =cut
350              
351             sub replace {
352 0     0 1   _put_post_stmts ('PUT', @_);
353             }
354              
355             =pod
356              
357             =item B
358              
359             I<$repo>->delete ([ I<$subj_uri>, I<$pred_uri>, I<$obj_uri> ], ...)
360              
361             This method removes the passed in triples from the repository. In that process, any combination of
362             the subject URI, the predicate or the object URI can be left C. That is interpreted as
363             wildcard which matches anything.
364              
365             Example: This deletes anything where the Stephansdom is the subject:
366              
367             $air->delete ([ '', undef, undef ])
368              
369             =cut
370              
371             sub delete {
372 0     0 1   _put_post_stmts ('DELETE', @_);
373             }
374              
375             =pod
376              
377             =item B
378              
379             I<@stmts> = I<$repo>->match ([ I<$subj_uri>, I<$pred_uri>, I<$obj_uri> ], ...)
380              
381             This method returns a list of all statements which match one of the triples provided
382             as parameter. Any C as URI within such a triple is interpreted as wildcard, matching
383             any other URI.
384              
385             (Since v0.06): The object part can now be a range of values. You simply provide an array reference
386             with the lower and the upper bound as values in the array, such as for example
387              
388             $repo->match ([ undef, undef, [ '"1"^^my:type', '"10"^^my:type' ] ]);
389              
390             B: Subject range queries and predicate range queries are not supported as RDF would not allow
391             literals at these places anyway.
392              
393             (Since v0.06): For AGv4 there is now a way to configure some options when fetching matching triples:
394             Simply provide as first parameter an options hash:
395              
396             $repo->match ({ limit => 10 }, [ undef, .....]);
397              
398             These options will apply to all passed in match patterns SEPARATELY, so that with several patterns
399             you might well get more than your limit.
400              
401             =cut
402              
403             sub match {
404 0     0 1   my $self = shift;
405 0 0         my $options = ref($_[0]) eq 'HASH' ? shift : {};
406              
407 0           my @stmts;
408 0           my $ua = $self->{CATALOG}->{SERVER}->{ua};
409 0           foreach my $w (@_) {
410 0           my $resp = $ua->get (_to_uri ($self->{path} . '/statements', $w, $options));
411 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
412 0           push @stmts, @{ from_json ($resp->content) };
  0            
413             }
414 0           return @stmts;
415             }
416              
417             sub _query {
418 0     0     my $self = shift;
419 0           my $query = shift;
420 0   0       my $lang = shift || 'sparql';
421 0           my %options = @_;
422              
423 0   0       $options{RETURN} ||= 'TUPLE_LIST'; # a good default
424 0           my $NAMED = 0;
425 0 0         ($NAMED, $options{RETURN}) = (1, 'TUPLE_LIST') if $options{RETURN} eq 'NAMED_TUPLE_LIST'; # store the info that we should return the names as well
426              
427 0           my @params;
428 0           push @params, "queryLn=$lang";
429 0           push @params, 'query='.uri_escape_utf8 ($query);
430 0 0         push @params, 'infer='.uri_escape_utf8 ($options{INFERENCING}) if defined $options{INFERENCING};
431            
432 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '?' . join ('&', @params) );
433 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
434              
435 0           my $json = from_json ($resp->content);
436 0           given ($options{RETURN}) {
437 0           when ('TUPLE_LIST') {
438 0 0         return $NAMED ? ($json) : @{ $json->{values} };
  0            
439             }
440 0           default { die };
  0            
441             }
442             }
443              
444             =pod
445              
446             =item B
447              
448             I<@tuples> = I<$repo>->sparql ('SELECT ...')
449              
450             I<@tuples> = I<$repo>->sparql ('SELECT ...' [, I<$option> => I<$value> ])
451              
452             This method takes a SPARQL query string and returns a list of tuples which the query produced from
453             the repository.
454              
455             B: At the moment only SELECT queries are supported.
456              
457             As additional options are accepted:
458              
459             =over
460              
461             =item C (default: C)
462              
463             In the case of C the result will be a sequence of (references to) arrays. All naming of
464             the individual columns is hereby lost. C really only returns the data (and not the names
465             within SELECT clause).
466              
467             (since v0.08)
468             C also returns a hash with the names (list reference) and the result sequence
469             (list reference, too).
470              
471             =item C (default: undef)
472              
473             [Since v0.08] With this option you can control the degree of inferencing used with this query.
474             By default, no inferencing is used, but if you pass in C, then the semantics of those
475             properties mentioned in C<.../doc/agraph-introduction.html#reasoning> are honored.
476              
477             =back
478              
479             =cut
480              
481             sub sparql {
482 0     0 1   my $self = shift;
483 0           my $query = shift;
484              
485 0           return _query ($self, $query, 'sparql', @_);
486             }
487              
488             =pod
489              
490             =item B (since v0.06)
491              
492             See C, but this is only supported for AGv4 servers.
493              
494             =cut
495              
496             sub prolog {
497 0     0 1   my $self = shift;
498 0           my $query = shift;
499              
500 0           return _query ($self, $query, 'prolog', @_);
501             }
502              
503              
504             =pod
505              
506             =back
507              
508             =head2 Namespace Support
509              
510             =over
511              
512             =item B
513              
514             I<%ns> = I<$repo>->namespaces
515              
516             This read-only function returns a hash containing the namespaces: keys
517             are the prefixes, values are the namespace URIs.
518              
519             B: No AllegroGraph I is honored at the moment.
520              
521             B: My current understanding is that AG does NOT support namespaces when you load data with
522             C or C, or try to match it with C. In that case, all URIs must be fully
523             expanded. Namespaces seem to work with SPARQL queries, though.
524              
525             =cut
526              
527             sub namespaces {
528 0     0 1   my $self = shift;
529 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '/namespaces');
530 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
531             return
532 0           map { $_->{prefix} => $_->{namespace} }
  0            
533 0           @{ from_json ($resp->content) };
534             }
535              
536             =pod
537              
538             =item B
539              
540             $uri = $repo->namespace ($prefix)
541              
542             $uri = $repo->namespace ($prefix => $uri)
543              
544             $repo->namespace ($prefix => undef)
545              
546             This method fetches, sets and deletes prefix/uri namespaces. If only the prefix is given,
547             it will look up the namespace URI. If the URI is provided as second parameter, it will set/overwrite
548             that prefix. If the second parameter is C, it will delete the namespace associated with it.
549              
550             B: No I is honored at the moment.
551              
552             =cut
553              
554             sub namespace {
555 0     0 1   my $self = shift;
556 0           my $prefix = shift;
557              
558 0           my $uri = $self->{path} . '/namespaces/' . $prefix;
559 0 0         if (scalar @_) { # there was a second argument!
560 0 0         if (my $nsuri = shift) {
561 0           my $requ = HTTP::Request->new ('PUT' => $uri, [ 'Content-Type' => 'text/plain' ], $nsuri);
562 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ);
563 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
564 0           return $nsuri;
565             } else {
566 0           my $requ = HTTP::Request->new ('DELETE' => $uri);
567 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ);
568 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
569             }
570             } else {
571 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($uri);
572 0 0         return undef if $resp->code == 404;
573 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
574 0   0       return $resp->content =~ m/^"?(.*?)"?$/ && $1;
575             }
576             }
577              
578             =pod
579              
580             =back
581              
582             =head2 GeoSpatial Support
583              
584             =over
585              
586             =item B
587              
588             I<@geotypes> = I<$repo>->geotypes
589              
590             This method returns a list of existing geotypes (in form of specially
591             crafted URIs). You need these URIs when you want to create locations
592             for them, or when you want to retrieve tuples within a specific area
593             (based on the geotype).
594              
595             =cut
596              
597             sub geotypes {
598 0     0 1   my $self = shift;
599 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '/geo/types');
600 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
601 0           return @{ from_json ($resp->content) };
  0            
602             }
603              
604             =pod
605              
606             =item B
607              
608             I<$coord> = I<$repo>->spherical (C, '5.2 degree');
609              
610             This method registers a spherical coordinate system on the server.
611              
612             B: With this version, no region can be specified (so this is always a complete sphere) and
613             only degrees are supported.
614              
615             =cut
616              
617             sub spherical {
618 0     0 1   my $self = shift;
619 0           my $region = shift;
620 0           my $scale = shift;
621              
622 15     15   55127 use Regexp::Common;
  15         44  
  15         119  
623 0 0         die "scale information must be of the form 5 mile, or 10 km, or similar"
624             unless ($scale =~ /($RE{num}{real})(\s+(degree|mile|km|radian))?/);
625              
626 0           my $stripW = $1;
627 0 0         my $unit = $3 if $2; # leave it undef otherwise
628              
629 0           my $url = new URI ($self->{path} . '/geo/types/spherical');
630 0 0         $url->query_form (stripWidth => $stripW,
631             ($unit
632             ? (unit => $unit) # be explicit
633             : ()
634             )
635             );
636              
637 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (POST $url);
638 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
639 0   0       return $resp->content =~ m/^"?(.*?)"?$/ && $1;
640             }
641              
642             =pod
643              
644             =item B
645              
646             I<$uri> = I<$repo>->cartesian ("100x100", I<$stripWidth>);
647              
648             I<$uri> = I<$repo>->cartesian ("100x100+10+10", I<$stripWidth>);
649              
650             I<$uri> = I<$repo>->cartesian (I<$minx>, I<$miny>, I<$maxx>, I<$maxy>, I<$stripWidth>);
651              
652             This method registers one new coordinate system at the server. The returned URI is later used as
653             reference to that system. The extensions of the system is provided either
654              
655             =over
656              
657             =item in the form C
658              
659             All numbers being floats. The X,Y offset part can be omitted.
660              
661             =item or, alternatively, as minx, miny, maxx, maxy quadruple
662              
663             Again all numbers being floats.
664              
665             =back
666              
667             The last parameter defines the resolution of the stripes, and gives the server optimization hints.
668             (See the general AG description for a deep explanation.)
669              
670             =cut
671              
672             sub cartesian {
673 0     0 1   my $self = shift;
674              
675 0           my $url = new URI ($self->{path} . '/geo/types/cartesian');
676              
677 15     15   84793 use Regexp::Common;
  15         43  
  15         73  
678 0 0         if ($_[0] =~ /($RE{num}{real})x($RE{num}{real})(\+($RE{num}{real})\+($RE{num}{real}))?/) {
679 0           shift;
680 0   0       my ($W, $H, $X, $Y) = ($1, $2, $4||0, $5||0);
      0        
681 0           my $stripW = shift;
682 0           $url->query_form (stripWidth => $stripW, xmin => $X, xmax => $X+$W, ymin => $Y, ymax => $Y+$H);
683             } else {
684 0           my ($X1, $Y1, $X2, $Y2, $stripW) = @_;
685 0           $url->query_form (stripWidth => $stripW, xmin => $X1, xmax => $X2, ymin => $Y1, ymax => $Y2);
686             }
687              
688 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (POST $url);
689 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
690 0   0       return $resp->content =~ m/^"?(.*?)"?$/ && $1;
691             }
692              
693             =pod
694              
695             =item B
696              
697             I<@ss> = I<$repo>->inBox (I<$geotype>, I<$predicate>, 35, 35, 65, 65, { limit => 10 });
698              
699             This method tries to find all triples which lie within a certain bounding box.
700              
701             The geotype is the one you create with C or C. The bounding box is given by the
702             bottom/left and the top/right corner coordinates. The optional C restricts the number of
703             triples you request.
704              
705             For cartesian coordinates you provide the bottom/left corner, and then the top/right one.
706              
707             For spherical coordinates you provide the longitude/latitude of the bottom/left corner, then
708             the longitude/latitude of the top/right one.
709              
710             =cut
711              
712             sub inBox {
713 0     0 1   my $self = shift;
714 0           my $geotype = shift;
715 0           my $pred = shift;
716 0           my ($xmin, $ymin, $xmax, $ymax) = @_;
717 0   0       my $options = $_[4] || {};
718              
719 0           my $url = new URI ($self->{path} . '/geo/box');
720 0 0         $url->query_form (type => $geotype,
721             predicate => $pred,
722             xmin => $xmin,
723             ymin => $ymin,
724             xmax => $xmax,
725             ymax => $ymax,
726             (defined $options->{limit}
727             ? (limit => $options->{limit})
728             : ())
729             );
730 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url);
731 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
732 0           return @{ from_json ($resp->content) };
  0            
733             }
734              
735             =pod
736              
737             =item B
738              
739             I<@ss> = I<$repo>->inCircle (I<$geotype>, I<$predicate>, 35, 35, 10, { limit => 10 });
740              
741             This method tries to find all triples which lie within a certain bounding circle.
742              
743             The geotype is the one you create with C or C. The bounding circle is given by
744             the center and the radius. The optional C restricts the number of triples you request.
745              
746             For cartesian coordinates you simply provide the X/Y coordinates of the circle center, and the
747             radius (in the unit as provided with the geotype.
748              
749             For spherical coordinates the center is specified with a longitude/latitude pair. The radius is also
750             interpreted along the provided geotype.
751              
752             B: As it seems, the circle MUST be totally within the range you specified for your
753             geotype. Otherwise AG will return 0 tuples.
754              
755             =cut
756              
757             sub inCircle {
758 0     0 1   my $self = shift;
759 0           my $geotype = shift;
760 0           my $pred = shift;
761 0           my ($x, $y, $radius) = @_;
762 0   0       my $options = $_[3] || {};
763              
764 0           my $url = new URI ($self->{path} . '/geo/circle');
765 0 0         $url->query_form (type => $geotype,
766             predicate => $pred,
767             x => $x,
768             y => $y,
769             radius => $radius,
770             (defined $options->{limit}
771             ? (limit => $options->{limit})
772             : ())
773             );
774 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url);
775 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
776 0           return @{ from_json ($resp->content) };
  0            
777             }
778              
779             =pod
780              
781             =item I (since v0.06)
782              
783             I<@ss> = I<$repo>->inPolygon (I<$coordtype>, I<$preduri>, I<@points>, { I<%options> })
784              
785             This method tries to identify all statements where the object is within a polygon defined by the
786             C array. Each point is simply an array reference with 2 entries (x,y, of course).
787              
788             The predicate URI defines which predicates should be considered. Do not leave it C. The
789             coordinate type is the one you will have generated before with C.
790              
791             The optional options can only contain C to restrict the number of tuples to be returned.
792              
793             For spherical coordinates make sure that you (a) provide longitude/latitude pairs and then that the
794             polygon is built clockwise.
795              
796             B: This is a somewhat expensive operation in terms of communication round-trips.
797              
798             =cut
799              
800             sub inPolygon {
801 0     0 1   my $self = shift;
802 0           my $geotype = shift;
803 0           my $pred = shift;
804 0           my @points;
805 0           while (ref($_[0]) eq 'ARRAY') {
806 15     15   25986 use RDF::AllegroGraph::Utils qw(coord2literal);
  15         35  
  15         22066  
807 0           push @points, coord2literal ($geotype, @{ shift @_ });
  0            
808             }
809 0   0       my $options = shift || {};
810              
811 0           my ($blank) = $self->blanks; # get one blank node
812              
813 0           my $url = new URI ($self->{path} . '/geo/polygon'); # build request to park polygon temporarily
814 0           $url->query_form (resource => $blank, # under the blank node
815             point => \@points # with these points expanded
816             );
817 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (PUT $url); # AGv4 does seem to require to have that URL encoded (with PUT??)
818 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
819              
820 0           $url = new URI ($self->{path} . '/geo/polygon'); # build request to park polygon temporarily
821 0 0         $url->query_form (polygon => $blank, # under the blank node
822             type => $geotype, # for this geotype
823             predicate => $pred, # and for this predicate
824             (defined $options->{limit}
825             ? (limit => $options->{limit})
826             : ())
827             );
828 0           $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url); # now we make the real query
829 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
830 0           return @{ from_json ($resp->content) };
  0            
831             }
832              
833              
834             =pod
835              
836             =item B
837              
838             This method will return a list of indices which the repository on the server understands. The list
839             contains strings of the form C which identify the bias of the index. See
840             L
841             for some introduction.
842              
843             B: These are NOT the indices which are active for that repository. See C for that.
844              
845             =cut
846              
847             sub valid_indices {
848 0     0 1   my $self = shift;
849 0           my $url = new URI ($self->{path} . '/indices?listValid=true');
850 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url);
851 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
852 0           return @{ from_json ($resp->content) };
  0            
853             }
854              
855             =pod
856              
857             =item B
858              
859             I<@idxs> = I<$rep>->indices ([ I ])
860              
861             This method always returns the current list of applied indices for that repository.
862              
863             Optionally you can pass in a list of changes you want in terms of indices, I in terms of
864             indices you want to add, or to remove. To add, say, a C index you would prefix it with a '+':
865              
866             $rep->indices ('+spogi')
867              
868             You can provide any number of such additions. In the same way you would a prefixed '-' to indicate
869             that you want an index to be deleted.
870              
871             =cut
872              
873             sub indices {
874 0     0 1   my $self = shift;
875 0           foreach my $sidx (@_) { # in the case we want changes to be made
876 0 0         if ($sidx =~ m{\-(.+)}) { # removal of indices
    0          
877 0           my $url = new URI ($self->{path} . '/indices/' . $1);
878 0           my $requ = HTTP::Request->new (DELETE => $url);
879 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ);
880 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
881              
882             } elsif ($sidx =~ m{\+(.+)}) { # adding of indices
883 0           my $url = new URI ($self->{path} . '/indices/' . $1);
884 0           my $requ = HTTP::Request->new (PUT => $url);
885 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ);
886 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
887              
888             } else { # not sure what this is => ignorance is bliss
889 0           warn "not sure what to do with '$sidx', ignoring ...";
890             }
891             }
892             # now collect the state of affairs from the server
893 0           my $url = new URI ($self->{path} . '/indices');
894 0           my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url);
895 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
896 0           return @{ from_json ($resp->content) };
  0            
897             }
898              
899              
900             =pod
901              
902             =item B
903              
904             I<$bool> = I<$repo>->bulk_loading_mode (C<1|0>)
905              
906             This method switches on and off the bulk loading capability of the repository. To enable it, pass in
907             C<1>, to turn it off pass in C<0>. In any case the current state is returned where C is
908             returned instead of C<0>.
909              
910             =cut
911              
912             sub _mode {
913 0     0     my $ua = shift;
914 0           my $path = shift;
915 0           my $val = shift;
916              
917 0 0         if (defined $val) {
918 0 0         if ($val) {
919 0           my $resp = $ua->request (PUT $path, 'Content' => $val);
920 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
921             } else {
922 0           my $requ = HTTP::Request->new (DELETE => $path);
923 0           my $resp = $ua->request ($requ);
924 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
925             }
926             }
927 0           my $resp = $ua->get ($path);
928 0 0         die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success;
929 0           return $resp->content eq 'true';
930             }
931              
932             sub bulk_loading_mode {
933 0     0 1   my $self = shift;
934 0           my $val = shift;
935              
936 0           return _mode ($self->{CATALOG}->{SERVER}->{ua}, $self->{path} . '/bulkMode', $val);
937              
938             }
939              
940             =pod
941              
942             =item B
943              
944             I<$bool> = I<$repo>->commit_mode (C<1|0>)
945              
946             Method to control the commit mode of a repository. Parameters and return values are like those for C.
947              
948             =cut
949              
950             sub commit_mode {
951 0     0 1   my $self = shift;
952 0           my $val = shift;
953              
954 0 0         return ! _mode ($self->{CATALOG}->{SERVER}->{ua}, $self->{path} . '/noCommit', defined $val ? abs($val-1) : undef);
955             }
956              
957             =pod
958              
959             =item B
960              
961             I<$bool> = I<$repo>->duplicate_suppression_mode (C<1|0>)
962              
963             Method to control the duplicate suppression behavior of a repository. Parameters and return values
964             are like those for C.
965              
966             =cut
967              
968             sub duplicate_suppression_mode {
969 0     0 1   my $self = shift;
970 0           my $val = shift;
971              
972 0           return _mode ($self->{CATALOG}->{SERVER}->{ua}, $self->{path} . '/deleteDuplicates', $val);
973             }
974              
975             =pod
976              
977             =back
978              
979             =cut
980              
981             our $VERSION = '0.07';
982              
983             1;
984              
985             __END__