File Coverage

blib/lib/Web/DataService.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             #
2             # DataService.pm
3             #
4             # This is a framework for building data service applications.
5             #
6             # Author: Michael McClennen
7              
8              
9 1     1   17954 use strict;
  1         2  
  1         71  
10              
11             require 5.012;
12              
13             =head1 NAME
14              
15             Web::DataService - a framework for building data service applications for the Web
16              
17             =head1 VERSION
18              
19             Version 0.20
20              
21             =head1 SYNOPSIS
22              
23             This module provides a framework for you to use in building data service
24             applications for the World Wide Web. Such applications sit between a data
25             storage and retrieval system on one hand and the Web on the other, and fulfill
26             HTTP-based data requests. Each valid request is handled by fetching or
27             storing the appropriate data using the backend data system and serializing the
28             output in a format such as JSON, CSV, or XML.
29              
30             Using the methods provided by this module, you start by defining a set of data
31             service elements: output formats, output blocks, vocabularies, and parameter
32             rules, followed by a set of data service nodes representing the various
33             operations to be provided by your service. Each of these objects is
34             configured by a set of attributes, optionally including documentation strings.
35             You continue by writing one or more roles whose methods will handle the
36             "meat" of each operation: talking to the backend data system to fetch and/or
37             store the relevant data, based on the parameter values provided in a data
38             service request.
39              
40             This module then handles the rest of the work necessary for handling each
41             request, including checking the parameter values, determining the output
42             format, and serializing the result. It also generates appropriate error
43             messages when necessary. Finally, it auto-generates documentation pages for
44             each operation based on the elements you have defined, so that your data
45             service is always fully and correctly documented.
46              
47             =cut
48              
49             package Web::DataService;
50              
51             our $VERSION = '0.20';
52              
53 1     1   5 use Carp qw( carp croak confess );
  1         7  
  1         62  
54 1     1   5 use Scalar::Util qw( reftype blessed weaken );
  1         5  
  1         77  
55 1     1   522 use POSIX qw( strftime );
  1         5105  
  1         5  
56 1     1   1131 use Sub::Identify;
  0            
  0            
57             use HTTP::Validate;
58              
59             use Web::DataService::Node;
60             use Web::DataService::Set;
61             use Web::DataService::Format;
62             use Web::DataService::Vocabulary;
63             use Web::DataService::Ruleset;
64             use Web::DataService::Render;
65             use Web::DataService::Output;
66             use Web::DataService::Execute;
67              
68             use Web::DataService::Request;
69             use Web::DataService::IRequest;
70             use Web::DataService::IDocument;
71             use Web::DataService::PodParser;
72              
73             use Moo;
74             use namespace::clean;
75              
76             with 'Web::DataService::Node', 'Web::DataService::Set',
77             'Web::DataService::Format', 'Web::DataService::Vocabulary',
78             'Web::DataService::Ruleset', 'Web::DataService::Render',
79             'Web::DataService::Output', 'Web::DataService::Execute';
80              
81              
82             our (@CARP_NOT) = qw(Web::DataService::Request Moo);
83              
84             HTTP::Validate->VERSION(0.45);
85              
86              
87             our @HTTP_METHOD_LIST = ('GET', 'HEAD', 'POST', 'PUT', 'DELETE');
88              
89             our @DEFAULT_METHODS = ('GET', 'HEAD');
90              
91             our %SPECIAL_FEATURE = (format_suffix => 1, documentation => 1,
92             doc_paths => 1, send_files => 1, strict_params => 1,
93             stream_output => 1);
94              
95             our @FEATURE_STANDARD = ('format_suffix', 'documentation', 'doc_paths',
96             'send_files', 'strict_params', 'stream_output');
97              
98             our @FEATURE_ALL = ('format_suffix', 'documentation', 'doc_paths',
99             'send_files', 'strict_params', 'stream_output');
100              
101             our %SPECIAL_PARAM = (selector => 'v', format => 'format', path => 'op',
102             document => 'document', show => 'show',
103             limit => 'limit', offset => 'offset',
104             count => 'count', vocab => 'vocab',
105             datainfo => 'datainfo', linebreak => 'lb',
106             header => 'header', save => 'save');
107              
108             our @SPECIAL_STANDARD = ('show', 'limit', 'offset', 'header', 'datainfo',
109             'count', 'vocab', 'linebreak', 'save');
110              
111             our @SPECIAL_SINGLE = ('selector', 'path', 'format', 'show', 'header',
112             'datainfo', 'vocab', 'linebreak', 'save');
113              
114             our @SPECIAL_ALL = ('selector', 'path', 'document', 'format', 'show',
115             'limit', 'offset', 'header', 'datainfo', 'count',
116             'vocab', 'linebreak', 'save');
117              
118             my (@DI_KEYS) = qw(data_provider data_source data_license license_url
119             documentation_url data_url access_time title);
120              
121              
122             # Execution modes
123              
124             our ($DEBUG, $ONE_REQUEST, $CHECK_LATER, $QUIET);
125              
126              
127             # Variables for keeping track of data service instances
128              
129             my (%KEY_MAP);
130             my (@WDS_INSTANCES);
131             my ($FOUNDATION);
132              
133              
134             # Attributes of a Web::DataService object
135              
136             has name => ( is => 'ro', required => 1,
137             isa => \&_valid_name );
138              
139             has parent => ( is => 'ro', init_arg => '_parent' );
140              
141             has features => ( is => 'ro', required => 1 );
142              
143             has special_params => ( is => 'ro', required => 1 );
144              
145             has foundation_plugin => ( is => 'ro' );
146              
147             has templating_plugin => ( is => 'lazy', builder => sub { $_[0]->_init_value('templating_plugin') } );
148              
149             has backend_plugin => ( is => 'lazy', builder => sub { $_[0]->_init_value('backend_plugin') } );
150              
151             has title => ( is => 'lazy', builder => sub { $_[0]->_init_value('title') } );
152              
153             has version => ( is => 'lazy', builder => sub { $_[0]->_init_value('version') } );
154              
155             has path_prefix => ( is => 'lazy', builder => sub { $_[0]->_init_value('path_prefix') } );
156              
157             has path_re => ( is => 'lazy', builder => sub { $_[0]->_init_value('path_re') } );
158              
159             has key => ( is => 'lazy', builder => sub { $_[0]->_init_value('key') } );
160              
161             has hostname => ( is => 'lazy', builder => sub { $_[0]->_init_value('hostname') } );
162              
163             has port => ( is => 'lazy', builder => sub { $_[0]->_init_value('port') } );
164              
165             has generate_url_hook => ( is => 'rw', isa => \&_code_ref );
166              
167             has ruleset_prefix => ( is => 'lazy', builder => sub { $_[0]->_init_value('ruleset_prefix') } );
168              
169             # has public_access => ( is => 'lazy', builder => sub { $_[0]->_init_value('public_access') } );
170              
171             has doc_suffix => ( is => 'lazy', builder => sub { $_[0]->_init_value('doc_suffix') } );
172              
173             has doc_index => ( is => 'lazy', builder => sub { $_[0]->_init_value('doc_index') } );
174              
175             has doc_template_dir => ( is => 'lazy', builder => sub { $_[0]->_init_value('doc_template_dir') } );
176              
177             has output_template_dir => ( is => 'lazy', builder => sub { $_[0]->_init_value('output_template_dir') } );
178              
179             has data_source => ( is => 'lazy', builder => sub { $_[0]->_init_value('data_source') } );
180              
181             has data_provider => ( is => 'lazy', builder => sub { $_[0]->_init_value('data_provider') } );
182              
183             has data_license => ( is => 'lazy', builder => sub { $_[0]->_init_value('data_license') } );
184              
185             has license_url => ( is => 'lazy', builder => sub { $_[0]->_init_value('license_url') } );
186              
187             has contact_name => ( is => 'lazy', builder => sub { $_[0]->_init_value('contact_name') } );
188              
189             has contact_email => ( is => 'lazy', builder => sub { $_[0]->_init_value('contact_email') } );
190              
191             has validator => ( is => 'ro', init_arg => undef );
192              
193              
194             # Validator methods for the data service attributes.
195              
196             sub _valid_name {
197              
198             die "not a valid name"
199             unless $_[0] =~ qr{ ^ [\w.:][\w.:-]* $ }xs;
200             }
201              
202              
203             sub _code_ref {
204              
205             die "must be a code ref"
206             unless ref $_[0] && reftype $_[0] eq 'CODE';
207             }
208              
209              
210             # BUILD ( )
211             #
212             # This method is called automatically after object initialization.
213              
214             sub BUILD {
215              
216             my ($self) = @_;
217            
218             local($Carp::CarpLevel) = 1; # We shouldn't have to do this, but
219             # Moo and Carp don't play well together.
220            
221             # If no path prefix was defined, make it the empty string.
222            
223             $self->{path_prefix} //= '';
224            
225             # Process the feature list
226             # ------------------------
227            
228             # These may be specified either as a listref or as a string with
229             # comma-separated values.
230            
231             my $features_value = $self->features;
232             my @features = ref $features_value eq 'ARRAY' ? @$features_value : split /\s*,\s*/, $features_value;
233            
234             ARG:
235             foreach my $o ( @features )
236             {
237             next unless defined $o && $o ne '';
238            
239             my $feature_value = 1;
240             my $key = $o;
241            
242             # If 'standard' was specified, enable the standard set of features.
243             # (But don't override any that have already been set or cleared
244             # explicitly.)
245            
246             if ( $o eq 'standard' )
247             {
248             foreach my $p ( @FEATURE_STANDARD )
249             {
250             $self->{feature}{$p} //= 1;
251             }
252            
253             next ARG;
254             }
255            
256             # If we get an argument that looks like 'no_feature', then disable
257             # the feature.
258            
259             elsif ( $o =~ qr{ ^ no_ (\w+) $ }xs )
260             {
261             $key = $1;
262             $feature_value = 0;
263             }
264            
265             # Now, complain if the user gives us something unrecognized.
266            
267             croak "unknown feature '$o'\n" unless $SPECIAL_FEATURE{$key};
268            
269             # Give this parameter the specified value (either on or off).
270             # Parameters not mentioned default to off, unless 'standard' was
271             # included.
272            
273             $self->{feature}{$key} = $feature_value;
274             }
275            
276             # Process the list of special parameters
277             # --------------------------------------
278            
279             # These may be specified either as a listref or as a string with
280             # comma-separated values.
281            
282             my $special_value = $self->special_params;
283             my @specials = ref $special_value eq 'ARRAY' ? @$special_value : split /\s*,\s*/, $special_value;
284            
285             ARG:
286             foreach my $s ( @specials )
287             {
288             next unless defined $s && $s ne '';
289             my $key = $s;
290             my $name = $SPECIAL_PARAM{$s};
291            
292             # If 'standard' was specified, enable the "standard" set of parameters
293             # with their default names (but don't override any that have already
294             # been enabled).
295            
296             if ( $s eq 'standard' )
297             {
298             foreach my $p ( @SPECIAL_STANDARD )
299             {
300             $self->{special}{$p} //= $SPECIAL_PARAM{$p};
301             }
302            
303             next ARG;
304             }
305            
306             # If we get an argument that looks like 'no_param', then disable
307             # the parameter.
308            
309             elsif ( $s =~ qr{ ^ no_ (\w+) $ }xs )
310             {
311             $key = $1;
312             $name = '';
313             }
314            
315             # If we get an argument that looks like 'param=name', then enable the
316             # feature 'param' but use 'name' as the accepted parameter name.
317            
318             elsif ( $s =~ qr{ ^ (\w+) = (\w+) $ }xs )
319             {
320             $key = $1;
321             $name = $2;
322             }
323            
324             # Now, complain if the user gives us something unrecognized, or an
325             # invalid parameter name.
326            
327             croak "unknown special parameter '$key'\n" unless $SPECIAL_PARAM{$key};
328             croak "invalid parameter name '$name' - bad character\n" if $name =~ /[^\w]/;
329            
330             # Enable this parameter with the specified name.
331            
332             $self->{special}{$key} = $name;
333             }
334            
335             # Make sure there are no feature or special parameter conflicts.
336            
337             croak "you may not specify the feature 'format_suffix' together with the special parameter 'format'"
338             if $self->{feature}{format_suffix} && $self->{special}{format};
339            
340             croak "you may not specify the feature 'doc_paths' together with the special parameter 'document'"
341             if $self->{feature}{doc_paths} && $self->{special}{document};
342            
343             $self->{feature}{doc_paths} = 0 unless $self->{feature}{documentation};
344            
345             # Check and configure the foundation plugin
346             # -----------------------------------------
347            
348             # It is necessary that each application uses a single foundation
349             # framework, no matter how many data service instances it includes. So if
350             # a foundation plugin was already specified, make sure that the current
351             # definition does not conflict.
352            
353             my $foundation_plugin = $self->foundation_plugin;
354            
355             if ( $FOUNDATION )
356             {
357             croak "conflicting foundation plugin $foundation_plugin - already set to $FOUNDATION\n"
358             if $foundation_plugin && $FOUNDATION ne $foundation_plugin;
359             }
360            
361             # If this is the first instance to be defined and a foundation plugin was
362             # specified in the initialization, make sure that it is correct.
363            
364             elsif ( $foundation_plugin )
365             {
366             eval "require $foundation_plugin" or croak $@;
367            
368             croak "class '$foundation_plugin' is not a valid foundation plugin: cannot find method 'read_config'\n"
369             unless $foundation_plugin->can('read_config');
370             }
371            
372             # Otherwise, if 'Dancer.pm' has already been required then install the
373             # corresponding plugin.
374            
375             elsif ( $INC{'Dancer.pm'} )
376             {
377             require Web::DataService::Plugin::Dancer or croak $@;
378             $foundation_plugin = 'Web::DataService::Plugin::Dancer';
379             }
380            
381             # Checks for other foundation frameworks will go here.
382            
383             # Otherwise, we cannot proceed. Give the user some idea of what to do.
384            
385             else
386             {
387             croak "could not find a foundation framework: try adding 'use Dancer;' \
388             before 'use Web::DataService' (and make sure that Dancer is installed)\n";
389             }
390            
391             # Now store this value and initialize the plugin.
392            
393             $FOUNDATION ||= $foundation_plugin;
394             $self->{foundation_plugin} ||= $FOUNDATION;
395             $self->_plugin_init('foundation_plugin');
396            
397             # From this point on, we will be able to read the configuration file
398             # (assuming that a valid one is present). So do so.
399            
400             $FOUNDATION->read_config($self);
401            
402             # Check and configure the templating plugin
403             # -----------------------------------------
404            
405             # Note that unlike the foundation plugin, different data service instances
406             # may use different templating plugins.
407            
408             # If a templating plugin was explicitly specified, either in the code
409             # or in the configuration file, check that it is valid.
410            
411             if ( my $templating_plugin = $self->templating_plugin )
412             {
413             eval "require $templating_plugin" or croak $@;
414            
415             croak "$templating_plugin is not a valid templating plugin: cannot find method 'render_template'\n"
416             unless $templating_plugin->can('render_template');
417             }
418            
419             # Otherwise, if 'Template.pm' has already been required then install the
420             # corresponding plugin.
421            
422             elsif ( $INC{'Template.pm'} && ! defined $self->templating_plugin )
423             {
424             require Web::DataService::Plugin::TemplateToolkit or croak $@;
425             $self->{templating_plugin} = 'Web::DataService::Plugin::TemplateToolkit';
426             }
427            
428             # Otherwise, templating will not be available.
429            
430             else
431             {
432             if ( $self->{feature}{documentation} )
433             {
434             unless ( $QUIET || $ENV{WDS_QUIET} )
435             {
436             warn "WARNING: no templating engine was specified, so documentation pages\n";
437             warn " and templated output will not be available.\n";
438             }
439             $self->{feature}{documentation} = 0;
440             $self->{feature}{doc_paths} = 0;
441             }
442            
443             $self->{templating_plugin} = 'Web::DataService::Plugin::Templating';
444             }
445            
446             # If we have a templating plugin, instantiate it for documentation and
447             # output.
448            
449             if ( defined $self->{templating_plugin} &&
450             $self->{templating_plugin} ne 'Web::DataService::Plugin::Templating' )
451             {
452             # Let the plugin do whatever initialization it needs to.
453            
454             $self->_plugin_init('templating_plugin');
455            
456             # If no document template directory was specified, use 'doc' if it
457             # exists and is readable.
458            
459             my $doc_dir = $self->doc_template_dir;
460             my $output_dir = $self->output_template_dir;
461            
462             unless ( defined $doc_dir )
463             {
464             my $default = $ENV{PWD} . '/doc';
465            
466             if ( -r $default )
467             {
468             $doc_dir = $default;
469             }
470            
471             elsif ( $self->{feature}{documentation} )
472             {
473             unless ( $QUIET || $ENV{WDS_QUIET} )
474             {
475             warn "WARNING: no document template directory was found, so documentation pages\n";
476             warn " will not be available. Try putting them in the directory 'doc',\n";
477             warn " or specifying the attribute 'doc_template_dir'.\n";
478             }
479             $self->{feature}{documentation} = 0;
480             $self->{feature}{doc_paths} = 0;
481             }
482             }
483            
484             # If we were given a directory for documentation templates, initialize
485             # an engine for evaluating them.
486            
487             if ( $doc_dir )
488             {
489             $doc_dir = $ENV{PWD} . '/' . $doc_dir
490             unless $doc_dir =~ qr{ ^ / }xs;
491            
492             croak "the documentation template directory '$doc_dir' is not readable: $!\n"
493             unless -r $doc_dir;
494            
495             $self->{doc_template_dir} = $doc_dir;
496            
497             $self->{doc_engine} =
498             $self->{templating_plugin}->new_engine($self, { template_dir => $doc_dir });
499            
500             # If the attributes doc_header, doc_footer, etc. were not set,
501             # check for the existence of defaults.
502            
503             my $doc_suffix = $self->{template_suffix} || '';
504            
505             $self->{doc_defs} //= $self->check_doc("doc_defs${doc_suffix}");
506             $self->{doc_header} //= $self->check_doc("doc_header${doc_suffix}");
507             $self->{doc_footer} //= $self->check_doc("doc_footer${doc_suffix}");
508             $self->{doc_default_template} //= $self->check_doc("doc_not_found${doc_suffix}");
509             $self->{doc_default_op_template} //= $self->check_doc("doc_op_template${doc_suffix}");
510             }
511            
512             # we were given a directory for output templates, initialize an
513             # engine for evaluating them as well.
514            
515             if ( $output_dir )
516             {
517             $output_dir = $ENV{PWD} . '/' . $output_dir
518             unless $output_dir =~ qr{ ^ / }xs;
519            
520             croak "the output template directory '$output_dir' is not readable: $!\n"
521             unless -r $output_dir;
522            
523             $self->{output_template_dir} = $output_dir;
524            
525             $self->{output_engine} =
526             $self->{templating_plugin}->new_engine($self, { template_dir => $output_dir });
527             }
528             }
529            
530             # Check and configure the backend plugin
531             # --------------------------------------
532            
533             # If a backend plugin was explicitly specified, check that it is valid.
534            
535             if ( my $backend_plugin = $self->backend_plugin )
536             {
537             eval "require $backend_plugin" or croak $@;
538            
539             croak "$backend_plugin is not a valid backend plugin: cannot find method 'get_connection'\n"
540             unless $backend_plugin->can('get_connection');
541             }
542            
543             # Otherwise, if 'Dancer::Plugin::Database' is available then select the
544             # corresponding plugin.
545            
546             elsif ( $INC{'Dancer.pm'} && $INC{'Dancer/Plugin/Database.pm'} && ! defined $self->backend_plugin )
547             {
548             $self->{backend_plugin} = 'Web::DataService::Plugin::Dancer';
549             }
550            
551             # Otherwise, we get the stub backend plugin which will throw an exception
552             # if called. If you still wish to access a backend data system, then you
553             # must either add code to the various operation methods to explicitly
554             # connect to it use one of the available hooks.
555            
556             else
557             {
558             $self->{backend_plugin} = 'Web::DataService::Plugin::Backend';
559             }
560            
561             # Let the backend plugin do whatever initialization it needs to.
562            
563             $self->_plugin_init('backend_plugin');
564            
565             # Register this instance so that we can select for it later
566             # ---------------------------------------------------------
567            
568             $self->_register_instance;
569            
570             # Check and set some attributes
571             # -----------------------------
572            
573             # The title must be non-empty, but we can't just label it 'required'
574             # because it might be specified in the configuration file.
575            
576             my $title = $self->title;
577            
578             croak "you must specify a title, either as a parameter to the data service definition or in the configuration file\n"
579             unless defined $title && $title ne '';
580            
581             # If no path_re was set, generate it from the path prefix.
582            
583             if ( ! $self->path_re )
584             {
585             my $prefix = $self->path_prefix;
586            
587             # If the prefix ends in '/', then generate a regexp that can handle
588             # either the prefix as given or the prefix string without the final /
589             # and without anything after it.
590            
591             if ( $prefix =~ qr{ (.*) [/] $ }xs )
592             {
593             $self->{path_re} = qr{ ^ [/] $1 (?: [/] (.*) | $ ) }xs;
594             }
595            
596             # Otherwise, generate a regexp that doesn't expect a / before the rest
597             # of the path.
598            
599             else
600             {
601             $self->{path_re} = qr{ ^ [/] $prefix (.*) }xs;
602             }
603             }
604            
605             # Create a default vocabulary, to be used in case no others are defined.
606            
607             $self->{vocab} = { 'null' =>
608             { name => 'null', use_field_names => 1, _default => 1, title => 'Null vocabulary',
609             doc_string => "This default vocabulary consists of the field names from the underlying data." } };
610            
611             $self->{vocab_list} = [ 'null' ];
612            
613             # We need to set defaults for 'doc_suffix' and 'index_name' so that we can
614             # handle 'doc_paths' if it is enabled. Application authors can turn
615             # either of these off by setting the value to the empty string.
616            
617             $self->{doc_suffix} //= '_doc';
618             $self->{doc_index} //= 'index';
619            
620             # Compute regexes from these suffixes.
621            
622             if ( $self->{doc_suffix} && $self->{doc_index} )
623             {
624             $self->{doc_path_regex} = qr{ ^ ( .* [^/] ) (?: $self->{doc_suffix} | / $self->{doc_index} | / ) $ }xs;
625             }
626            
627             elsif ( $self->{doc_suffix} )
628             {
629             $self->{doc_path_regex} = qr{ ^ ( .* [^/] ) (?: $self->{doc_suffix} | / ) $ }xs;
630             }
631            
632             elsif ( $self->{doc_index} )
633             {
634             $self->{doc_path_regex} = qr{ ^ ( .* [^/] ) (?: / $self->{doc_index} | / $ }xs;
635             }
636            
637             # Create a new HTTP::Validate object so that we can do parameter
638             # validations.
639            
640             $self->{validator} = HTTP::Validate->new();
641            
642             $self->{validator}->validation_settings(allow_unrecognized => 1)
643             unless $self->{feature}{strict_params};
644            
645             # Add a few other necessary fields.
646            
647             $self->{path_defs} = {};
648             $self->{node_attrs} = {};
649             $self->{attr_cache} = {};
650             $self->{format} = {};
651             $self->{format_list} = [];
652             $self->{subservice} = {};
653             $self->{subservice_list} = [];
654             }
655              
656              
657             # _init_value ( param )
658             #
659             # Return the initial value for the specified parameter. If it is already
660             # present as a direct attribute, return that. Otherwise, look it up in the
661             # hash of values from the configuration file. If those fail, check our parent
662             # (if we have a parent).
663              
664             sub _init_value {
665            
666             my ($self, $param) = @_;
667            
668             die "empty configuration parameter" unless defined $param && $param ne '';
669            
670             # First check to see if we have this attribute specified directly.
671             # Otherwise, check whether it is in our _config hash. Otherwise,
672             # if we have a parent then check its direct attributes and _config hash.
673             # Otherwise, return undefined.
674            
675             my $ds_name = $self->name;
676            
677             return $self->{$param} if defined $self->{$param};
678             return $self->{_config}{$ds_name}{$param} if defined $self->{_config}{$ds_name}{$param};
679             return $self->{parent}->_init_value($param) if defined $self->{parent};
680             return $self->{_config}{$param} if defined $self->{_config}{$param};
681            
682             return;
683             }
684              
685              
686             # _plugin_init ( plugin )
687             #
688             # If the specified plugin has an 'initialize_service' method, call it with
689             # ourselves as the argument.
690              
691             sub _plugin_init {
692              
693             my ($self, $plugin) = @_;
694            
695             return unless defined $self->{$plugin};
696            
697             no strict 'refs';
698            
699             if ( $self->{$plugin}->can('initialize_plugin') && ! ${"$self->{$plugin}::_INITIALIZED"} )
700             {
701             $self->{$plugin}->initialize_plugin($self);
702             ${"$self->{$plugin}::_INITIALIZED"} = 1;
703             }
704            
705             if ( defined $self->{$plugin} && $self->{$plugin}->can('initialize_service') )
706             {
707             $self->{$plugin}->initialize_service($self);
708             }
709             }
710              
711              
712             # config_value ( param )
713             #
714             # Return the value (if any) specified for this parameter in the configuration
715             # file. If not found, check the configuration for our parent (if we have a
716             # parent). This differs from _init_value above in that direct attributes are
717             # not checked.
718              
719             sub config_value {
720              
721             my ($self, $param) = @_;
722            
723             die "empty configuration parameter" unless defined $param && $param ne '';
724            
725             # First check to see whether this parameter is in our _config hash.
726             # Otherwise, if we have a parent then check its _config hash. Otherwise,
727             # return undefined.
728            
729             my $ds_name = $self->name;
730            
731             return $self->{_config}{$ds_name}{$param} if defined $self->{_config}{$ds_name}{$param};
732             return $self->{parent}->config_value($param) if defined $self->{parent};
733             return $self->{_config}{$param} if defined $self->{_config}{$param};
734            
735             return;
736             }
737              
738              
739             # has_feature ( name )
740             #
741             # Return true if the given feature is set for this data service, undefined
742             # otherwise.
743              
744             sub has_feature {
745            
746             my ($self, $name) = @_;
747            
748             croak "has_feature: unknown feature '$name'\n" unless $SPECIAL_FEATURE{$name};
749             return $self->{feature}{$name};
750             }
751              
752              
753             # special_param ( name )
754             #
755             # If the given special parameter is enabled for this data service, return the
756             # parameter name. Otherwise, return the undefined value.
757              
758             sub special_param {
759            
760             my ($self, $name) = @_;
761            
762             croak "special_param: unknown special parameter '$name'\n" unless $SPECIAL_PARAM{$name};
763             return $self->{special}{$name};
764             }
765              
766              
767             # valid_name ( name )
768             #
769             # Return true if the given name is valid according to the Web::DataService
770             # specification, false otherwise.
771              
772             sub valid_name {
773            
774             my ($self, $name) = @_;
775            
776             return 1 if defined $name && !ref $name && $name =~ qr{ ^ [\w][\w.:-]* $ }xs;
777             return; # otherwise
778             }
779              
780              
781             # _register_instance ( )
782             #
783             # Register this instance's key and path prefix so that the application code can
784             # later locate the appropriate service for handling each request.
785              
786             sub _register_instance {
787              
788             my ($self) = @_;
789            
790             # Add this to the list of defined data service instances.
791            
792             push @WDS_INSTANCES, $self;
793            
794             # If the attribute 'key' was defined, add it to the key map.
795            
796             if ( my $key = $self->key )
797             {
798             croak "You cannot register two data services with the key '$key'\n"
799             if $KEY_MAP{$key};
800            
801             $KEY_MAP{$key} = $self;
802             }
803             }
804              
805              
806             # select ( outer )
807             #
808             # Return the data service instance that is appropriate for this request, or
809             # return an error if no instance could be matched. This should be called as a
810             # class method.
811              
812             sub select {
813            
814             my ($class, $outer) = @_;
815            
816             my $param;
817            
818             # Throw an error unless we have at least one data service instance to work with.
819            
820             croak "No data service instances have been defined" unless @WDS_INSTANCES;
821            
822             my $instance = $WDS_INSTANCES[0];
823            
824             # If the special parameter 'selector' is active, then we will use its
825             # value to determine the appropriate data service instance. We check the
826             # first instance defined because all instances in this application should
827             # either enable or disable this parameter alike.
828            
829             if ( $param = $instance->{special}{selector} )
830             {
831             my $key = $FOUNDATION->get_param($outer, $param);
832            
833             # If the parameter value matches a data service instance, return that.
834            
835             if ( defined $key && $KEY_MAP{$key} )
836             {
837             return $KEY_MAP{$key};
838             }
839            
840             # Otherwise, if the URL path is empty or just '/', return the first
841             # instance defined.
842            
843             my $path = $FOUNDATION->get_request_path($outer);
844            
845             if ( !defined $path || $path eq '' || $path eq '/' )
846             {
847             return $instance;
848             }
849            
850             # Otherwise, return an error message specifying the proper values.
851            
852             my @keys = sort keys %KEY_MAP;
853             my $good_values = join(', ', map { "v=$_" } @keys);
854            
855             if ( defined $key && $key ne '' )
856             {
857             die "400 Invalid version '$key' - you must specify one of the following parameters: $good_values\n";
858             }
859            
860             else
861             {
862             die "400 You must specify a data service version using one of the following parameters: $good_values\n";
863             }
864             }
865            
866             # Otherwise, check the request path against each data service instance to
867             # see if we can figure out which one to use by means of the regexes
868             # stored in the path_re attribute.
869            
870             else
871             {
872             my $path = $FOUNDATION->get_request_path($outer);
873            
874             foreach my $ds ( @WDS_INSTANCES )
875             {
876             if ( defined $ds->{path_re} && $path =~ $ds->{path_re} )
877             {
878             return $ds;
879             }
880             }
881            
882             # If none of the instances match this path, then throw a 404 (Not
883             # Found) exception.
884            
885             die "404";
886             }
887             }
888              
889              
890              
891             sub get_connection {
892            
893             my ($self) = @_;
894            
895             croak "get_connection: no backend plugin was loaded\n"
896             unless defined $self->{backend_plugin};
897             return $self->{backend_plugin}->get_connection($self);
898             }
899              
900              
901              
902             sub set_mode {
903            
904             my ($self, @modes) = @_;
905            
906             foreach my $mode (@modes)
907             {
908             if ( $mode eq 'debug' )
909             {
910             $DEBUG = 1 unless $QUIET || $ENV{WDS_QUIET};
911             }
912            
913             elsif ( $mode eq 'one_request' )
914             {
915             $ONE_REQUEST = 1;
916             }
917            
918             elsif ( $mode eq 'late_path_check' )
919             {
920             $CHECK_LATER = 1;
921             }
922            
923             elsif ( $mode eq 'quiet' )
924             {
925             $QUIET = 1;
926             }
927             }
928             }
929              
930              
931             sub is_mode {
932              
933             my ($self, $mode) = @_;
934            
935             return 1 if $mode eq 'debug' && $DEBUG;
936             return 1 if $mode eq 'one_request' && $ONE_REQUEST;
937             return 1 if $mode eq 'late_path_check' && $CHECK_LATER;
938             return 1 if $mode eq 'quiet' && $QUIET;
939             return;
940             }
941              
942              
943             # generate_url ( attrs )
944             #
945             # Generate a URL according to the specified attributes:
946             #
947             # node Generates a documentation URL for the specified data service node
948             #
949             # op Generates an operation URL for the specified data service node
950             #
951             # path Generates a URL for this exact path (with the proper prefix added)
952             #
953             # format Specifies the format to be included in the URL
954             #
955             # params Species the parameters, if any, to be included in the URL
956             #
957             # fragment Specifies a fragment identifier to add to the generated URL
958             #
959             # type Specifies the type of URL to generate: 'abs' for an
960             # absolute URL, 'rel' for a relative URL, 'site' for
961             # a site-relative URL (starts with '/'). Defaults to 'site'.
962              
963             sub generate_site_url {
964              
965             my $self = shift;
966            
967             my $attrs = ref $_[0] eq 'HASH' ? $_[0]
968             : scalar(@_) % 2 == 0 ? { @_ }
969             : croak "generate_url: odd number of arguments";
970            
971             # If a custom routine was specified for this purpose, call it.
972            
973             if ( $self->{generate_url_hook} )
974             {
975             return &{$self->{generate_url_hook}}($self, $attrs);
976             }
977            
978             # Otherwise, construct the URL according to the feature set of this data
979             # service.
980            
981             my $path = $attrs->{node} || $attrs->{op} || $attrs->{path} || '';
982             my $format = $attrs->{format};
983             my $type = $attrs->{type} || 'site';
984            
985             unless ( defined $path )
986             {
987             carp "generate_url: you must specify a URL path\n";
988             }
989            
990             elsif ( ! $attrs->{path} && $path =~ qr{ (.*) [.] ([^.]+) $ }x )
991             {
992             $path = $1;
993             $format = $2;
994             }
995            
996             $format = 'html' if $attrs->{node} && ! (defined $format && $format eq 'pod');
997            
998             my @params = ref $attrs->{params} eq 'ARRAY' ? @{$attrs->{params}}
999             : defined $attrs->{params} ? split(/&/, $attrs->{params})
1000             : ();
1001            
1002             my ($has_format, $has_selector);
1003            
1004             foreach my $p ( @params )
1005             {
1006             $has_format = 1 if $self->{special}{format} && $p =~ qr{ ^ $self->{special}{format} = \S }x;
1007             $has_selector = 1 if $self->{special}{selector} && $p =~ qr{ ^ $self->{special}{selector} = \S }xo;
1008             }
1009            
1010             # if ( defined $attrs->{node} && ref $attrs->{node} eq 'ARRAY' )
1011             # {
1012             # push @params, @{$attrs->{node}};
1013             # croak "generate_url: odd number of parameters is not allowed\n"
1014             # if scalar(@_) % 2;
1015             # }
1016            
1017             # First, check if the 'fixed_paths' feature is on. If so, then the given
1018             # documentation or operation path is converted to a parameter and the appropriate
1019             # fixed path is substituted.
1020            
1021             if ( $self->{feature}{fixed_paths} )
1022             {
1023             if ( $attrs->{node} )
1024             {
1025             push @params, $self->{special}{document} . "=$path" unless $path eq '/';
1026             $path = $self->{doc_url_path};
1027             }
1028            
1029             elsif ( $attrs->{op} )
1030             {
1031             push @params, $self->{special}{op} . "=$path";
1032             $path = $self->{operation_url_path};
1033             }
1034             }
1035            
1036             # Otherwise, we can assume that the URL paths will reflect the given path.
1037             # So next, check if the 'format_suffix' feature is on.
1038            
1039             if ( $self->{feature}{format_suffix} )
1040             {
1041             # If this is a documentation URL, then add the documentation suffix if
1042             # the "doc_paths" feature is on. Also add the format. But not if the
1043             # path is '/'.
1044            
1045             if ( $attrs->{node} && $path ne '/' )
1046             {
1047             $path .= $self->{doc_suffix} if $self->{feature}{doc_paths};
1048             $path .= ".$format";
1049             }
1050            
1051             # If this is an operation URL, we just add the format if one was
1052             # specified.
1053            
1054             elsif ( $attrs->{op} )
1055             {
1056             $path .= ".$format" if $format;
1057             }
1058            
1059             # A path URL is not modified.
1060             }
1061            
1062             # Otherwise, if the feature 'doc_paths' is on then we still need to modify
1063             # the paths.
1064            
1065             elsif ( $self->{feature}{doc_paths} )
1066             {
1067             if ( $attrs->{node} && $path ne '/' )
1068             {
1069             $path .= $self->{doc_suffix};
1070             }
1071             }
1072            
1073             # If the special parameter 'format' is enabled, then we need to add it
1074             # with the proper format name.
1075            
1076             if ( $self->{special}{format} && ! $has_format && ! $attrs->{path} )
1077             {
1078             # If this is a documentation URL, then add a format parameter unless
1079             # the format is either 'html' or empty.
1080            
1081             if ( $attrs->{node} && $format && $format ne 'html' )
1082             {
1083             push @params, $self->{special}{format} . "=$format";
1084             }
1085            
1086             # If this is an operation URL, we add the format unless it is empty.
1087            
1088             elsif ( $attrs->{op} )
1089             {
1090             push @params, $self->{special}{format} . "=$format" if $format;
1091             }
1092            
1093             # A path URL is not modified.
1094             }
1095            
1096             # If the special parameter 'selector' is enabled, then we need to add it
1097             # with the proper data service key.
1098            
1099             if ( $self->{special}{selector} && ! $has_selector )
1100             {
1101             my $key = $self->key;
1102             push @params, $self->{special}{selector} . "=$key";
1103             }
1104            
1105             # If the path is '/', then turn it into the empty string.
1106            
1107             $path = '' if $path eq '/';
1108            
1109             # Now assemble the URL. If the type is not 'relative' then we start with
1110             # the path prefix. Otherwise, we start with the given path.
1111            
1112             my $url;
1113            
1114             if ( $type ne 'rel' )
1115             {
1116             $url = '/' . $self->{path_prefix} . $path;
1117             }
1118            
1119             else
1120             {
1121             $url = $path;
1122             }
1123            
1124             # Add the parameters and fragment, if any.
1125            
1126             if ( @params )
1127             {
1128             $url .= '?';
1129             my $sep = '';
1130            
1131             while ( @params )
1132             {
1133             $url .= $sep . shift(@params);
1134             $sep = '&';
1135             }
1136             }
1137            
1138             if ( $attrs->{fragment} )
1139             {
1140             $url .= "#$attrs->{fragment}";
1141             }
1142            
1143             # Return the resulting URL.
1144            
1145             return $url;
1146             }
1147              
1148              
1149             # node_link ( path, title )
1150             #
1151             # Generate a link in POD format to the documentation for the given path. If
1152             # $title is defined, use that as the link title. Otherwise, if the path has a
1153             # 'doc_title' attribute, use that.
1154             #
1155             # If something goes wrong, generate a warning and return the empty string.
1156              
1157             sub node_link {
1158            
1159             my ($self, $path, $title) = @_;
1160            
1161             return 'I>' unless defined $path;
1162            
1163             # Generate a "node:" link for this path, which will be translated into an
1164             # actual URL later.
1165            
1166             if ( defined $title && $title ne '' )
1167             {
1168             return "L<$title|node:$path>";
1169             }
1170            
1171             elsif ( $title = $self->node_attr($path, 'title') )
1172             {
1173             return "L<$title|node:$path>";
1174             }
1175            
1176             else
1177             {
1178             return "I>";
1179             }
1180             }
1181              
1182              
1183             # base_url ( )
1184             #
1185             # Return the base URL for this data service, in the form "http://hostname/".
1186             # If the attribute 'port' was specified for this data service, include that
1187             # too.
1188              
1189             sub base_url {
1190            
1191             my ($self) = @_;
1192            
1193             carp "CALL: base_url\n";
1194            
1195             #return $FOUNDATION->get_base_url;
1196            
1197             my $hostname = $self->{hostname} // '';
1198             my $port = $self->{port} ? ':' . $self->{port} : '';
1199            
1200             return "http://${hostname}${port}/";
1201             }
1202              
1203              
1204             # root_url ( )
1205             #
1206             # Return the root URL for this data service, in the form
1207             # "http://hostname/prefix/".
1208              
1209             sub root_url {
1210              
1211             my ($self) = @_;
1212            
1213             carp "CALL: root_url\n";
1214            
1215             #return $FOUNDATION->get_base_url . $self->{path_prefix};
1216            
1217             my $hostname = $self->{hostname} // '';
1218             my $port = $self->{port} ? ':' . $self->{port} : '';
1219            
1220             return "http://${hostname}${port}/$self->{path_prefix}";
1221             }
1222              
1223              
1224             # execution_class ( primary_role )
1225             #
1226             # This method is called to create a class in which we can execute requests.
1227             # We need to create one of these for each primary role used in the
1228             # application.
1229             #
1230             # This class needs to have two roles composed into it: the first is
1231             # Web::DataService::Request, which provides methods for retrieving the request
1232             # parameters, output fields, etc.; the second is the "primary role", written
1233             # by the application author, which provides methods to implement one or more
1234             # data service operations. We cannot simply use Web::DataService::Request as
1235             # the base class, as different requests may require composing in different
1236             # primary roles. We cannot use the primary role as the base class, because
1237             # then any method conflicts would be resolved in favor of the primary role.
1238             # This would compromise the functionality of Web::DataService::Request, which
1239             # needs to be able to call its own methods reliably.
1240             #
1241             # The best way to handle this seems to be to create a new, empty class and
1242             # then compose in both the primary role and Web::DataService::Request using a
1243             # single 'with' request. This way, an exception will be thrown if the two
1244             # sets of methods conflict. This new class will be named using the prefix
1245             # 'REQ::', so that if the primary role is 'Example' then the new class will be
1246             # 'REQ::Example'.
1247             #
1248             # Any other roles needed by the primary role must also be composed in. We
1249             # also must check for an 'initialize' method in each of these roles, and call
1250             # it if present. As a result, we cannot simply rely on transitive composition
1251             # by having the application author use 'with' to include one role inside
1252             # another. Instead, the role author must indicate additional roles as
1253             # follows:
1254             #
1255             # package MyRole;
1256             # use Moo::Role;
1257             #
1258             # our(@REQUIRES_ROLE) = qw(SubRole1 SubRole2);
1259             #
1260             # Both the primary role and all required roles will be properly initialized,
1261             # which includes calling their 'initialize' method if one exists. This will
1262             # be done only once per role, no matter how many contexts it is used in. Each
1263             # of the subsidiary roles will be composed one at a time into the request
1264             # execution class.
1265              
1266             sub execution_class {
1267              
1268             my ($self, $primary_role) = @_;
1269            
1270             no strict 'refs';
1271            
1272             croak "you must specify a non-empty primary role"
1273             unless defined $primary_role && $primary_role ne '';
1274            
1275             croak "you must first load the module '$primary_role' before using it as a primary role"
1276             unless $primary_role eq 'DOC' || %{ "${primary_role}::" };
1277            
1278             my $request_class = "REQ::$primary_role";
1279            
1280             # $DB::single = 1;
1281            
1282             # First check to see if this class has already been created. Return
1283             # immediately if so.
1284            
1285             return $request_class if exists ${ "${request_class}::" }{_CREATED};
1286            
1287             # Otherwise create the new class and compose in Web::DataService::Request
1288             # and the primary role. Then compose in any secondary roles, one at a time.
1289            
1290             my $secondary_roles = "";
1291            
1292             foreach my $role ( @{ "${primary_role}::REQUIRES_ROLE" } )
1293             {
1294             croak "create_request_class: you must first load the module '$role' \
1295             before using it as a secondary role for '$primary_role'"
1296             unless %{ "${role}::" };
1297            
1298             $secondary_roles .= "with '$role';\n";
1299             }
1300            
1301             my $string = " package $request_class;
1302             use Try::Tiny;
1303             use Scalar::Util qw(reftype);
1304             use Carp qw(carp croak);
1305             use Moo;
1306             use namespace::clean;
1307            
1308             use base 'Web::DataService::Request';
1309             with 'Web::DataService::IRequest', '$primary_role';
1310             $secondary_roles
1311            
1312             our(\$_CREATED) = 1";
1313            
1314             my $result = eval $string;
1315            
1316             # Now initialize the primary role, unless of course it has already been
1317             # initialized. This will also cause any uninitialized secondary roles to
1318             # be initialized.
1319            
1320             $self->initialize_role($primary_role) unless $primary_role eq 'DOC';
1321            
1322             return $request_class;
1323             }
1324              
1325              
1326             # documentation_class ( primary_role )
1327             #
1328             # This method is called to create a class into which we can bless an object
1329             # that represents a documentation request. This will potentially be called
1330             # once for each different primary role in the data service application, plus
1331             # once to create a generic documentation class not based on any role.
1332             #
1333             # The classes created here must include all of the methods necessary for
1334             # generating documentation, including all of the methods in the indicated
1335             # role(s).
1336              
1337             sub documentation_class {
1338              
1339             my ($self, $primary_role) = @_;
1340            
1341             no strict 'refs';
1342            
1343             # First check to see if the necessary class has already been created.
1344             # Return immediately if so, because we have nothing left to do. If no
1345             # primary role was specified, the name of the class will be "DOC".
1346            
1347             my $request_class = $primary_role ? "DOC::$primary_role" : "DOC";
1348            
1349             return $request_class if exists ${ "${request_class}::" }{_CREATED};
1350            
1351             # Make sure that a package corresponding to the specified primary role
1352             # actually exists.
1353            
1354             croak "you must first load the module '$primary_role' before using it as a primary role"
1355             if $primary_role && ! %{ "${primary_role}::" };
1356            
1357             # If the primary role has not yet been initialized, do so. This will also
1358             # cause any uninitialized secondary roles to be initialized.
1359            
1360             $self->initialize_role($primary_role) if $primary_role;
1361            
1362             # Now create the new class and compose into it both
1363             # Web::DataService::Request and the primary role. By doing these together
1364             # we will generate an error if there are any method conflicts between
1365             # these packages. Also compose in any secondary roles, one at a time.
1366             # Any method conflicts here will be silently resolved in favor of the
1367             # primary role and/or Web::DataService::Request.
1368            
1369             my $primary_with = "";
1370             my $secondary_roles = "";
1371            
1372             if ( $primary_role )
1373             {
1374             $primary_with = ", '$primary_role'";
1375            
1376             foreach my $role ( @{ "${primary_role}::REQUIRES_ROLE" } )
1377             {
1378             croak "create_request_class: you must first load the module '$role' \
1379             before using it as a secondary role for '$primary_role'"
1380             unless %{ "${role}::" };
1381            
1382             $secondary_roles .= "with '$role';\n";
1383             }
1384             }
1385            
1386             my $string = " package $request_class;
1387             use Carp qw(carp croak);
1388             use Moo;
1389             use namespace::clean;
1390            
1391             use base 'Web::DataService::Request';
1392             with 'Web::DataService::IDocument' $primary_with;
1393             $secondary_roles
1394            
1395             our(\$_CREATED) = 1";
1396            
1397             my $result = eval $string;
1398            
1399             return $request_class;
1400             }
1401              
1402              
1403             # initialize_role ( role )
1404             #
1405             # This method calls the 'initialize' method of the indicated role, but first
1406             # it recursively processes every role required by that role. The intialize
1407             # method is only called once per role per execution of this program, no matter
1408             # how many contexts it is used in.
1409              
1410             sub initialize_role {
1411            
1412             my ($self, $role) = @_;
1413            
1414             no strict 'refs';
1415            
1416             #
1417            
1418             # If we have already initialized this role, there is nothing else we need
1419             # to do.
1420            
1421             return if ${ "${role}::_INITIALIZED" };
1422             ${ "${role}::_INITIALIZED" } = 1;
1423            
1424             # If this role requires one or more secondary roles, then initialize them
1425             # first (unless they have already been initialized).
1426            
1427             foreach my $required ( @{ "${role}::REQUIRES_ROLE" } )
1428             {
1429             $self->initialize_role($required);
1430             }
1431            
1432             # Now, if the role has an initialization routine, call it. We need to do
1433             # this after the previous step because this role's initialization routine
1434             # may depend upon side effects of the required roles' initialization routines.
1435            
1436             if ( $role->can('initialize') )
1437             {
1438             print STDERR "Initializing $role for data service $self->{name}\n" if $DEBUG || $self->{DEBUG};
1439             $role->initialize($self);
1440             }
1441            
1442             my $a = 1; # we can stop here when debugging
1443             }
1444              
1445              
1446             # set_scratch ( key, value )
1447             #
1448             # Store the specified value in the "scratchpad" for this data service, under
1449             # the specified key. This can be used to store data, configuration
1450             # information, etc. for later use by data operation methods.
1451              
1452             sub set_scratch {
1453            
1454             my ($self, $key, $value) = @_;
1455            
1456             return unless defined $key && $key ne '';
1457            
1458             $self->{scratch}{$key} = $value;
1459             }
1460              
1461              
1462             # get_scratch ( key, value )
1463             #
1464             # Retrieve the value corresponding to the specified key from the "scratchpad" for
1465             # this data service.
1466              
1467             sub get_scratch {
1468            
1469             my ($self, $key, $value) = @_;
1470            
1471             return unless defined $key && $key ne '';
1472            
1473             return $self->{scratch}{$key};
1474             }
1475              
1476              
1477             # data_info ( )
1478             #
1479             # Return the following pieces of information:
1480             # - The name of the data source
1481             # - The license under which the data is made available
1482              
1483             sub data_info {
1484            
1485             my ($self) = @_;
1486            
1487             my $access_time = strftime("%a %F %T GMT", gmtime);
1488            
1489             my $title = $self->{title};
1490             my $data_provider = $self->data_provider;
1491             my $data_source = $self->data_source;
1492             my $data_license = $self->data_license;
1493             my $license_url = $self->license_url;
1494            
1495             my $result = {
1496             title => $title,
1497             data_provider => $data_provider,
1498             data_source => $data_source,
1499             data_license => $data_license,
1500             license_url => $license_url,
1501             access_time => $access_time };
1502            
1503             return $result;
1504             }
1505              
1506              
1507             # data_info_keys
1508             #
1509             # Return a list of keys into the data_info hash, in the proper order to be
1510             # listed in a response message.
1511              
1512             sub data_info_keys {
1513            
1514             return @DI_KEYS;
1515             }
1516              
1517              
1518             # contact_info ( )
1519             #
1520             # Return the data service attributes "contact_name" and "contact_email",
1521             # as a hash whose keys are "name" and "email".
1522              
1523             sub contact_info {
1524            
1525             my ($self) = @_;
1526            
1527             my $result = {
1528             name => $self->contact_name,
1529             email => $self->contact_email };
1530            
1531             return $result;
1532             }
1533              
1534              
1535             # get_base_path ( )
1536             #
1537             # Return the base path for the current data service, derived from the path
1538             # prefix. For example, if the path prefix is 'data', the base path is
1539             # '/data/'.
1540              
1541             # sub get_base_path {
1542            
1543             # my ($self) = @_;
1544            
1545             # my $base = '/';
1546             # $base .= $self->{path_prefix} . '/'
1547             # if defined $self->{path_prefix} && $self->{path_prefix} ne '';
1548            
1549             # return $base;
1550             # }
1551              
1552              
1553             sub debug {
1554              
1555             my ($self) = @_;
1556            
1557             return $DEBUG || $self->{DEBUG};
1558             }
1559              
1560              
1561             =head1 METHODS
1562              
1563             =head2 CONFIGURATION
1564              
1565             The following methods are used to configure a web data service application.
1566             For a list of the available attributes for each method, see
1567             L. For detailed instructions on how to set
1568             up a data service application, see L. These
1569             configuration methods will be called at the start of your data service
1570             application. The method C is a class method; the others are all instance
1571             methods, to be called on the resulting Web::DataService object(s).
1572              
1573             =head3 new ( { attributes ... } )
1574              
1575             This class method defines a new data service instance. Calling it is
1576             generally the first step in configuring a web dataservice application. The
1577             available attributes are described in L. The
1578             attribute C is required; the others are optional, and may be specified
1579             in the application configuration file instead.
1580              
1581             =head3 define_vocab ( { attributes ... }, documentation ... )
1582              
1583             Defines one or more vocabularies, using the specified attributes and
1584             documentation strings. Each vocabulary represents a different set of terms by
1585             which to label and express the returned data.
1586              
1587             =head3 define_format ( { attributes ... }, documentation ... )
1588              
1589             Defines one or more output formats, using the specified attributes and
1590             documentation strings. Each of these formats represents a configuration of
1591             one of the available serialization modules.
1592              
1593             =head3 define_node ( { attributes ... }, documentation ... )
1594              
1595             Defines one or more data service nodes, using the specified attributes and
1596             documentation strings. Each of these nodes represents either an operation
1597             provided by the data service or a page of documentation.
1598              
1599             =head3 define_block ( block_name, { attributes ... }, documentation ... )
1600              
1601             Defines an output block with the given name, containing the specified output
1602             fields and documentation.
1603              
1604             =head3 define_set ( set_name, { attributes ... }, documentation ... )
1605              
1606             Defines a named set of values, possibly with a mapping to some other list of
1607             values. These can be used to specify the acceptable values for request
1608             parameters, to translate data values into different vocabularies, or to
1609             specify the available sets of optional output for various kinds of requests.
1610              
1611             =head3 define_ruleset ( ruleset_name, { attributes ... }, documentation ... )
1612              
1613             Define a ruleset with the given name, containing the specified rules and
1614             documentation. These are used to validate parameter values.
1615              
1616             =head2 EXECUTION
1617              
1618             The following methods are available for you to use in the part of your code
1619             that handles incoming requests. This will typically be inside one or more
1620             "route handlers" or "controllers" defined using the foundation framework.
1621              
1622             =head3 handle_request ( outer, [ attrs ] )
1623              
1624             A call to this method directs the Web::DataService framework to handle the
1625             current request. Depending on the request, one of the data service operation
1626             methods that you have written may be called as part of this process.
1627              
1628             The first argument must be the "outer" request object generated by the
1629             foundation framework. This allows the Web::DataService code to obtain details
1630             about the request and to compose the response using the functionality provided
1631             by that framework. The Web::DataService code will create an "inner" object of
1632             class Web::DataService::Request, with attributes derived from the current
1633             request along with the data service node (if any) that matches it. If no data
1634             service node matches the current request, a 404 error response will be
1635             returned to the client.
1636              
1637             You may provide a second optional argument, which must be a hashref of request
1638             attributes (see L). These will be used to
1639             initialize the request object, overriding any automatically determined
1640             attributes.
1641              
1642             =head3 new_request ( outer, [ attrs ] )
1643              
1644             If you wish more control over the request-handling process than is provided by
1645             L, you may instead call
1646             this method. It returns an object of class Web::DataService::Request, derived
1647             as described for C.
1648              
1649             You can then examine and possibly alter any of the request attributes, before
1650             calling L.
1651              
1652             =head3 execute_request ( request )
1653              
1654             This method may be called to execute a request, once the request object has
1655             been created and examined. The argument must be an object of class
1656             Web::DataService::Request from a previous call to
1657             L.
1658              
1659             =head3 node_attr ( path, attribute )
1660              
1661             Returns the specified attribute of the node with the specified path, if the
1662             specified path and attribute are both defined. Returns C otherwise.
1663             You can use this to test whether a particular node is in fact defined, or to
1664             retrieve any node attribute.
1665              
1666             You will rarely need to call this method, since for any request the relevant
1667             attributes of the matching node will be automatically used to instantiate the
1668             request object. In almost all cases, you will instead use the attribute
1669             accessor methods of the request object.
1670              
1671             =head3 get_connection
1672              
1673             If a backend plugin is available, obtains a connection handle from it. You
1674             can use this method when initializing your data classes, if your
1675             initialization process requires communication with the backend. You are not
1676             required to use this mechanism, however, and may contact the backend in any
1677             way you choose.
1678              
1679             =head3 has_feature ( feature_name )
1680              
1681             Returns a true value if the specified
1682             L
1683             is enabled for this data service. Returns false otherwise.
1684              
1685             =head3 special_param ( parameter_name )
1686              
1687             If the specified
1688             L
1689             [inst]"> is enabled for this data service, returns the parameter name which
1690             clients use. This may be different from the internal name by which this
1691             parameter is known, but will always be a true value. Returns false if this
1692             parameter is not enabled.
1693              
1694             =head3 base_url
1695              
1696             Returns the base URL of this data service, in the form
1697             "http[s]://hostname[:port]/". Most of the URLs included in the documentation
1698             pages will be relative to this base.
1699              
1700             =head3 root_url
1701              
1702             Returns the root URL of this data service, in the form
1703             "http[s]://hostname[:port]/[prefix/] where I is the
1704             L defined for this
1705             data service.
1706              
1707             =head3 generate_site_url
1708              
1709             This method works the same as the L
1710              
1711             method of L. However, it can only generate URLs of
1712             type "rel" or "site". If you want to generate an absolute URL, use the latter
1713             method.
1714              
1715             =head3 accessor methods
1716              
1717             Each of the data service
1718             L
1719             is provided with an accessor method. This method returns the attribute value,
1720             but cannot be used to set it. All data service attributes must be set when
1721             the data service object is instantiated with C, either specified
1722             directly in that call or looked up in the application configuration file
1723             provided by the foundation framework.
1724              
1725             =head2 DOCUMENTATION
1726              
1727             The following methods are available for you to use in generating
1728             documentation. If you use the included documentation templates, you will
1729             probably not need to call them directly.
1730              
1731             =head3 document_vocab ( path, { options ... } )
1732              
1733             Return a documentation string in POD for the vocabularies that are allowed for
1734             the specified path. The optional C hash may include the following:
1735              
1736             =over 4
1737              
1738             =item all
1739              
1740             Document all vocabularies, not just those allowed for the path.
1741              
1742             =item extended
1743              
1744             Include the documentation string for each voabulary.
1745              
1746             =back
1747              
1748             =head3 document_formats ( path, { options ... } )
1749              
1750             Return a string containing documentation in POD for the formats that are
1751             allowed for the specified path. The optional C hash may include the
1752             following:
1753              
1754             =over 4
1755              
1756             =item all
1757              
1758             Documents all formats, not just those allowed for the path.
1759              
1760             =item extended
1761              
1762             Includes the documentation string for each format.
1763              
1764             =back
1765              
1766             =head2 MISCELLANEOUS
1767              
1768             =head3 valid_name ( name )
1769              
1770             Returns true if the given string is valid as a Web::DataService name. This
1771             means that it begins with a word character and includes only word characters
1772             plus the punctuation characters ':', '-' and '.'.
1773              
1774             =head3 set_mode ( mode ... )
1775              
1776             You can call this either as a class method or as an instance method; it has
1777             a global effect either way. This method turns on one or more of the
1778             following modes:
1779              
1780             =over 4
1781              
1782             =item debug
1783              
1784             Produces additional debugging output to STDERR.
1785              
1786             =item one_request
1787              
1788             Configures the data service to satisfy one request and then exit. This is
1789             generally used for testing purposes.
1790              
1791             =back
1792              
1793             You will typically call this at application startup time.
1794              
1795             =head1 AUTHOR
1796              
1797             mmcclenn "at" cpan.org
1798              
1799             =head1 BUGS
1800              
1801             Please report any bugs or feature requests to C, or through
1802             the web interface at L. I will be notified, and then you'll
1803             automatically be notified of progress on your bug as I make changes.
1804              
1805             =head1 COPYRIGHT & LICENSE
1806              
1807             Copyright 2014 Michael McClennen, all rights reserved.
1808              
1809             This program is free software; you can redistribute it and/or modify it
1810             under the same terms as Perl itself.
1811              
1812             =cut
1813              
1814              
1815             package Web::DataService::Plugin::Templating;
1816              
1817             use Carp qw(croak);
1818              
1819             sub render_template { croak "render_template: no templating plugin was specified\n"; }
1820              
1821              
1822             package Web::DataService::Plugin::Backend;
1823              
1824             use Carp qw(croak);
1825              
1826             sub get_connection { croak "get_connection: no backend plugin was specified"; }
1827              
1828              
1829             1;