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