File Coverage

blib/lib/Plack/App/DAIA.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1 4     4   67866 use strict;
  4         6  
  4         95  
2 4     4   12 use warnings;
  4         5  
  4         161  
3             package Plack::App::DAIA;
4             {
5             $Plack::App::DAIA::VERSION = '0.45_1';
6             }
7             #ABSTRACT: DAIA Server as Plack application
8              
9 4     4   14 use feature ':5.10';
  4         5  
  4         434  
10              
11 4     4   348 use parent 'Plack::Component';
  4         204  
  4         18  
12 4     4   36686 use LWP::Simple qw(get);
  4         170384  
  4         28  
13 4     4   629 use Encode;
  4         5  
  4         203  
14 4     4   1557 use JSON;
  4         24903  
  4         25  
15 4     4   1998 use DAIA;
  0            
  0            
16             use Scalar::Util qw(blessed);
17              
18             use Plack::Util::Accessor qw(xsd xslt warnings code idformat initialized html);
19             use Plack::Middleware::Static;
20             use File::ShareDir qw(dist_dir);
21              
22             use Plack::Request;
23              
24             our %FORMATS = DAIA->formats;
25              
26             sub prepare_app {
27             my $self = shift;
28             return if $self->initialized;
29              
30             $self->warnings(1) unless defined $self->warnings;
31             $self->idformat(qr{^.*$}) unless defined $self->idformat;
32              
33             if ($self->html) {
34             $self->html( Plack::Middleware::Static->new(
35             path => qr{daia\.(xsl|css)$|xmlverbatim\.xsl$|icon/[a-z0-9_-]+\.png$},
36             root => dist_dir('Plack-App-DAIA')
37             ));
38             $self->xslt( '/daia.xsl' ) unless $self->xslt; # TODO: fix base path
39             }
40              
41             $self->init;
42              
43             $self->initialized(1);
44             }
45              
46             sub init {
47             # initialization hook
48             }
49              
50             sub call {
51             my ($self, $env) = @_;
52             my $req = Plack::Request->new($env);
53              
54             my $id = $req->param('id') // '';
55             my $invalid_id = '';
56             my %parts;
57              
58             if ( $self->html and $id eq '' ) {
59             my $resp = $self->html->_handle_static( $env );
60             if ($resp and $resp->[0] eq 200) {
61             return $resp;
62             }
63             }
64              
65             if ( $id ne '' and ref $self->idformat ) {
66             if ( ref $self->idformat eq 'Regexp' ) {
67             if ( $id =~ $self->idformat ) {
68             %parts = %+; # named capturing groups
69             } else {
70             $invalid_id = $id;
71             $id = "";
72             }
73             }
74             }
75              
76             my $format = lc($req->param('format')) || "";
77              
78             if (!$format) {
79             # TODO: guess format via content negotiation
80             }
81            
82             my $status = 200;
83             my $daia = $self->retrieve( $id, %parts );
84              
85             if (!$daia) {
86             $daia = DAIA::Response->new;
87             $status = 500;
88             }
89              
90             if ( $self->warnings ) {
91             if ( $invalid_id ne '' ) {
92             $daia->addMessage( 'en' => 'unknown identifier format', errno => 400 );
93             } elsif ( $id eq "" ) {
94             $daia->addMessage( 'en' => 'please provide a document identifier', errno => 400 );
95             }
96             }
97              
98             $self->as_psgi( $status, $daia, $format, $req->param('callback') );
99             }
100              
101             sub retrieve {
102             my $self = shift;
103             return $self->code ? $self->code->(@_) : undef;
104             }
105              
106             sub as_psgi {
107             my ($self, $status, $daia, $format, $callback) = @_;
108             my ($content, $type);
109              
110             $type = $FORMATS{$format} unless $format eq 'xml';
111             $content = $daia->serialize($format) if $type;
112              
113             if (!$content) {
114             $type = "application/xml; charset=utf-8";
115             if ( $self->warnings ) {
116             if ( not $format ) {
117             $daia->addMessage( 'en' => 'please provide an explicit parameter format=xml', 300 );
118             } elsif ( $format ne 'xml' ) {
119             $daia->addMessage( 'en' => 'unknown or unsupported format', 300 );
120             }
121             }
122             $content = $daia->xml( header => 1, xmlns => 1, ( $self->xslt ? (xslt => $self->xslt) : () ) );
123             } elsif ( $type =~ qr{^application/javascript} and ($callback || '') =~ /^[\w\.\[\]]+$/ ) {
124             $content = "$callback($content)";
125             }
126              
127             return [ $status, [ "Content-Type" => $type ], [ encode('utf8',$content) ] ];
128             }
129              
130             1;
131              
132              
133             __END__
134             =pod
135              
136             =head1 NAME
137              
138             Plack::App::DAIA - DAIA Server as Plack application
139              
140             =head1 VERSION
141              
142             version 0.45_1
143              
144             =head1 SYNOPSIS
145              
146             To quickly hack a DAIA server, create a simple C<app.psgi>:
147              
148             use Plack::App::DAIA;
149              
150             Plack::App::DAIA->new( code => sub {
151             my $id = shift;
152             # ...construct and return DAIA object
153             } );
154              
155             To create your own DAIA server, you should better derive from this class:
156              
157             package Your::App;
158             use parent 'Plack::App::DAIA';
159              
160             sub retrieve {
161             my ($self, $id, %parts) = @_;
162              
163             # construct DAIA object (you must extend this in your application)
164             my $daia = DAIA::Response->new;
165              
166             return $daia;
167             };
168              
169             1;
170              
171             Then create an C<app.psgi> that returns an instance of your class:
172              
173             use Your::App;
174             Your::App->new;
175              
176             You can also mix this application with L<Plack> middleware.
177              
178             It is highly recommended to test your services! Testing is made as easy as
179             possible with the L<provedaia> command line script.
180              
181             This module contains a dummy application C<app.psgi> and a more detailed
182             example C<examples/daia-ubbielefeld.pl>.
183              
184             =head1 DESCRIPTION
185              
186             This module implements a L<DAIA> server as PSGI application. It provides
187             serialization in DAIA/XML and DAIA/JSON and automatically adds some warnings
188             and error messages. The core functionality must be implemented by deriving
189             from this class and implementing the method C<retrieve>. The following
190             serialization formats are supported by default:
191              
192             =over 4
193              
194             =item xml
195              
196             DAIA/XML format (default)
197              
198             =item json
199              
200             DAIA/JSON format
201              
202             =item rdfjson
203              
204             DAIA/RDF in RDF/JSON.
205              
206             =back
207              
208             In addition you get DAIA/RDF in several RDF formats (C<rdfxml>,
209             C<turtle>, and C<ntriples> if L<RDF::Trine> is installed. If L<RDF::NS> is
210             installed, you also get known namespace prefixes for RDF/Turtle format.
211             Furthermore the output formats C<svg> and C<dot> are supported if
212             L<RDF::Trine::Exporter::GraphViz> is installed to visualize RDF graphs
213             (you may need to make sure that C<dot> is in your C<$ENV{PATH}>).
214              
215             =head1 METHODS
216              
217             =head2 new ( [%options] )
218              
219             Creates a new DAIA server. Supported options are
220              
221             =over 4
222              
223             =item xslt
224              
225             Path of a DAIA XSLT client to attach to DAIA/XML responses. Not required if
226             option "html" is set.
227              
228             =item html
229              
230             Enable HTML client for DAIA/XML via XSLT. The client is returned in form of
231             three files (C<daia.xsl>, C<daia.css>, C<xmlverbatim.xsl>) and approriate
232             icons, that are all shipped with this module.
233              
234             =item xsd
235              
236             Path of a DAIA XML Schema to validate DAIA/XML response.
237              
238             =item warnings
239              
240             Enable warnings in the DAIA response (enabled by default).
241              
242             =item code
243              
244             Code reference to the 'retrieve' method if you prefer not to create a
245             module derived from this module.
246              
247             =item idformat
248              
249             Optional regular expression to validate identifiers. Invalid identifiers
250             are set to the empty string before they are passed to the 'retrieve'
251             method. In addition an error message "unknown identifier format" is
252             added to the response, if warnings are enabled.
253              
254             It is recommended to use regular expressions with named capturing groups
255             as introduced in Perl 5.10. The named parts are also passed to the
256             retrieve method. For instance:
257              
258             idformat => qr{^ (?<prefix>[a-z]+) : (?<local>.+) $}x
259              
260             will give you C<$parts{prefix}> and C<$parts{local}> in the retrieve method.
261              
262             =item initialized
263              
264             Stores whether the application had been initialized.
265              
266             =back
267              
268             =head2 retrieve ( $id [, %parts ] )
269              
270             Must return a status and a L<DAIA::Response> object. Override this method
271             if you derive an application from Plack::App::DAIA. By default it either
272             calls the retrieve code, as passed to the constructor, or returns undef,
273             so a HTTP 500 error is returned.
274              
275             This method is passed the original query identifier and a hash of named
276             capturing groups from your identifier format.
277              
278             =head2 init
279              
280             This method is called by L<Plack::Component>::prepare_app, once before the
281             first request. You can define this method in you subclass as initialization
282             hook, for instance to set default option values. Initialization during runtime
283             can be triggered by setting C<initialized> to false.
284              
285             =head2 as_psgi ( $status, $daia [, $format [, $callback ] ] )
286              
287             Serializes a L<DAIA::Response> in some DAIA serialization format (C<xml> by
288             default) and returns a a PSGI response with given HTTP status code.
289              
290             =head1 SEE ALSO
291              
292             L<Plack::App::DAIA::Validator> and L<Plack::DAIA::Test>.
293              
294             =head1 AUTHOR
295              
296             Jakob Voss
297              
298             =head1 COPYRIGHT AND LICENSE
299              
300             This software is copyright (c) 2012 by Jakob Voss.
301              
302             This is free software; you can redistribute it and/or modify it under
303             the same terms as the Perl 5 programming language system itself.
304              
305             =cut
306