Skip to content

Commit

Permalink
source union and cascade
Browse files Browse the repository at this point in the history
  • Loading branch information
nichtich committed Jun 20, 2011
1 parent f2a525a commit 089e18e
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 46 deletions.
21 changes: 11 additions & 10 deletions lib/RDF/Light.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ sub prepare_app {
ref $self->formats eq 'HASH'
or carp 'formats must be a hash reference';

$self->source( RDF::Light::Source::source($self->source) );

$self->via_param(1) unless defined $self->via_param;
}

Expand Down Expand Up @@ -115,18 +117,17 @@ sub retrieve {
my $self = shift;
my $env = shift;

my $sources = $self->source or return;
$sources = [ $sources ] unless ref $sources and ref $sources eq 'ARRAY';
my $src = $self->source or return;

foreach my $src (@$sources) {
# foreach my $src (@$sources) {
my $rdf = try {
if ( UNIVERSAL::isa( $src, 'CODE' ) ) {
$src->($env);
} elsif ( UNIVERSAL::isa( $src, 'RDF::Trine::Model' ) ) {
$src->bounded_description( iri($env->{'rdflight.uri'}) );
} elsif ( UNIVERSAL::can( $src, 'retrieve' ) ) {
# if ( UNIVERSAL::isa( $src, 'CODE' ) ) {
# $src->($env);
# } elsif ( UNIVERSAL::isa( $src, 'RDF::Trine::Model' ) ) {
# $src->bounded_description( iri($env->{'rdflight.uri'}) );
# } elsif ( UNIVERSAL::can( $src, 'retrieve' ) ) {
$src->retrieve( $env );
}
# }
} catch {
$_ =~ s/ at.+ line \d+.?\n?//; # TODO: is there a cleaner way?
$env->{'rdflight.error'} = $_;
Expand All @@ -142,7 +143,7 @@ sub retrieve {
} else {
$env->{'rdflight.error'} = 'Invalid source';
}
}
# }

return;
}
Expand Down
138 changes: 105 additions & 33 deletions lib/RDF/Light/Source.pm
Original file line number Diff line number Diff line change
Expand Up @@ -4,77 +4,149 @@ package RDF::Light::Source;

use Plack::Request;
use RDF::Trine qw(iri statement);
use Scalar::Util qw(blessed);

use parent 'Exporter';
use Carp;
our @EXPORT = qw(dummy_source);
our @EXPORT_OK = qw(source is_source dummy_source);

our $rdf_type = iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
our $rdfs_resource = iri('http://www.w3.org/2000/01/rdf-schema#Resource');
our $rdfs_Resource = iri('http://www.w3.org/2000/01/rdf-schema#Resource');

sub new {
my $class = 'RDF::Light::Source';

my $code = sub { };

if ( @_ == 1 ) {
my $src = shift;
if (blessed $src and $src->isa('RDF::Light::Source')) {
return $src; # don't wrap
} elsif ( blessed $src and $src->isa('RDF::Trine::Model') ) {
$code = sub {
my $uri = RDF::Light::uri( shift );
$src->bounded_description( iri( $uri ) );
};
} elsif ( ref $src and ref $src eq 'CODE' ) {
$code = $src;
} elsif (not defined $src) {
$code = sub { }; # TODO: warn?
} else {
croak 'expected RDF::Light::Source, RDF::Trine::Model, or code reference';
}
} # TODO?
#my $class = shift;
# if ( grep { !is_source($_) } @_ ) {
# croak 'Expected a RDF::Light::Source, RDF::Trine::Model, or CODE ref';
# }
#
bless { code => $code }, $class;
}

sub retrieve {
my ($self, $env) = @_;
$self->{code}->( $env );
}

sub source { new(@_) }

sub is_source {
my $s = shift;
(ref $s and ref $s eq 'CODE') or blessed($s) and
($s->isa('RDF::Light::Source') or $s->isa('RDF::Trine::Model'));
}

sub pipe {
my ($self, $next) = @_;
return source( sub {
my $res = $self->retrieve(shift);
return unless defined $res;
# TODO: if not empty: retrieve next and create union
} );
}

sub dummy_source {
my $env = shift;
my $uri = RDF::Light::uri( $env );

my $model = RDF::Trine::Model->temporary_model;
$model->add_statement( statement( iri($uri), $rdf_type, $rdfs_resource ) );
$model->add_statement( statement( iri($uri), $rdf_type, $rdfs_Resource ) );

return $model;
}

sub retrieve {
return RDF::Trine::Model->temporary_model;
}

1;

__END__
=head1 DESCRIPTION
A Source is a code reference that gets HTTP requests and returns RDF models.
This is similar to L<PSGI> applications, which return HTTP responses. In
contrast to a PSGI application, a source must return an object of type
L<RDF::Trine::Model> or L<RDF::Trine::Iterator>.
A source returns RDF data as instance of L<RDF::Trine::Model> or
L<RDF::Trine::Iterator> when queried by a L<PSGI> requests. This is
similar to PSGI applications, which return HTTP responses instead of
RDF data. RDF::Light supports three types of sources: code references,
instances of RDF::Light::Source, and instances of RDF::Trine::Model.
In general you do not need to directly use this package. You can use any
of the following three as source: a code reference, an object that supports
the method 'retrieve', or an instance of RDF::Trine::Model:
=head1 SYNOPSIS
# 1. code reference
# RDF::Light::Source as source
$src = RDF::Light::Source->new( @other_sources );
my $source = sub {
# retrieve RDF data
$rdf = $src->retrieve( $env );
$rdf = $src->( $env ); # use source as code reference
# code reference as source
$src = sub {
my $env = shift;
my $uri = RDF::Light::uri( $env );
my $model = RDF::Trine::Model->temporary_model;
add_some_statements( $model );
add_some_statements( $uri, $model );
return $model;
};
# 2. object with 'retrieve' method (duck typing)
# RDF::Trine::Model as source returns same as the following sub:
$src = $model;
$src = sub {
my $uri = RDF::Light::uri( shift );
return $model->bounded_description( RDF::Trine::iri( $uri ) );
}
# Check whether $src is a valid source
RDF::Light::Source::is_source( $src );
# It is recommended to define your source as package
package MySource;
use parent 'Exporter';
use parent 'RDF::Light::Source';
sub retrieve {
my ($self, $env) = @_;
my $query = $self->build_query_from( $env );
my $iterator = $self->get_triples_from( $query );
return $iterator; # or model
}
my ($self, $env) = shift;
# ..your logic here...
}
=head1 METHODS
=head2 new ( [ @sources ] )
=head2 source ( [ @sources ] )
Returns a new source, possibly by wrapping a set of other sources. Croaks if
any if the passes sources is no RDF::Light::Source, RDF::Trine::Model, or
CODE reference. This constructor can also be exported as function C<source>:
# ...end of package, in your application just use:
use MySource;
my $source = MySource->new( ... );
use RDF::Light::Source qw(source);
# 3. model instance
$src = source( @args ); # short form
$src = RDF::Light::Source->source( @args ); # equivalent
$src = RDF:Light::Source->new( @args ); # explicit constructor
my $model = RDF::Trine::Model->new( ... );
my $source = $model; # RDF::Light will call $model->bounded_description
=head2 is_source
This package contains the following function which is exported by default:
Checks whether the object is a valid source. C<< $source->is_source >> is
always true, but you can also use and export this method as function:
=over 4
use RDF::Light::Source qw(is_source);
is_source( $src );
=item dummy_source
Expand Down
58 changes: 58 additions & 0 deletions lib/RDF/Light/Source/Cascade.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use strict;
use warnings;
package RDF::Light::Source::Cascade;

use parent 'RDF::Light::Source';
use Scalar::Util 'blessed';
use Carp 'croak';

our @EXPORT = qw(cascade);

sub new {
my $class = shift;
bless [ map { RDF::Light::Source::source($_) } @_ ], $class;
}

sub retrieve {
my ($self, $env) = @_;

foreach my $src ( @$self ) {
my $rdf = $src->retrieve( $env ); # TODO: failsafe mode?

next unless defined $rdf;
if ( blessed $rdf and $rdf->isa('RDF::Trine::Model') ) {
return $rdf if $rdf->size > 0;
} elsif ( blessed $rdf and $rdf->isa('RDF::Trine::Iterator') ) {
return $rdf if $rdf->peek;
} else {
# TODO: croak or warn
}
}

return;
}

sub cascade { RDF::Light::Source::Cascade->new(@_) }

1;

__END__
=head1 DESCRIPTION
This L<RDF::Light::Source> returns the first non-empty response of a given
sequence of sources. It exports the function 'cascade' as constructor shortcut.
=head1 SYNOPSIS
use RDF::Light::Source::Cascade;
$src = cascade(@sources); # shortcut
$src = RDF::Light::Source::Cascade->new( @sources ); # explicit
$rdf = $src->retrieve( $env );
=head2 SEE ALSO
L<RDF::Light::Source::Union>
=cut
58 changes: 58 additions & 0 deletions lib/RDF/Light/Source/Union.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use strict;
use warnings;
package RDF::Light::Source::Union;

use parent 'RDF::Light::Source';
use Carp ();

our @EXPORT = qw(union);

sub new {
my $class = shift;
bless [ map { RDF::Light::Source::source($_) } @_ ], $class;
}

sub retrieve { # TODO: try/catch errors?
my ($self, $env) = @_;

my $result;

if ( @$self == 1 ) {
$result = $self->[0]->retrieve( $env );
} elsif( @$self > 1 ) {
$result = RDF::Trine::Model->temporary_model;
foreach my $src ( @$self ) { # TODO: parallel processing?
my $rdf = $src->retrieve( $env );
next unless defined $rdf;
$rdf = $rdf->as_stream unless $rdf->isa('RDF::Trine::Iterator');
$result->add_iterator( $rdf );
}
}

return $result;
}

sub union { RDF::Light::Source::Union->new(@_) }

1;

__END__
=head1 DESCRIPTION
This L<RDF::Light::Source> returns the union of responses of a set of sources.
It exports the function 'union' as constructor shortcut.
=head1 SYNOPSIS
use RDF::Light::Source::Union;
$src = union(@sources); # shortcut
$src = RDF::Light::Source::Union->new( @sources ); # explicit
$rdf = $src->retrieve( $env );
=head2 SEE ALSO
L<RDF::Light::Source::Cascade>
=cut
15 changes: 13 additions & 2 deletions t/00_compile.t
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
use strict;
use Test::More tests => 1;

BEGIN { use_ok 'RDF::Light' }
my @modules;

BEGIN { @modules = qw(
RDF::Light
RDF::Light::Graph
RDF::Light::Source
RDF::Light::Source::Union
RDF::Light::Source::Cascade
); }

use Test::More tests => scalar @modules;

use_ok($_) for @modules;
3 changes: 2 additions & 1 deletion t/30_sources.t
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use Plack::Test;
use Plack::Builder;
use RDF::Light;
use RDF::Light::Source;
use RDF::Light::Source::Union;
use RDF::Trine::Model;
use RDF::Trine qw(iri statement);

Expand Down Expand Up @@ -41,7 +42,7 @@ test_app
app => builder {
enable "+RDF::Light",
base => "http://example.com/",
source => [ $example_model, \&dummy_source ];
source => union( $example_model, \&dummy_source );
$not_found;
},
tests => [{
Expand Down
Loading

0 comments on commit 089e18e

Please sign in to comment.