# # Web::DataService::Request # # A base class that implements a data service for the PaleoDB. This can be # subclassed to produce any necessary data service. For examples, see # TaxonQuery.pm and CollectionQuery.pm. # # Author: Michael McClennen use strict; package Web::DataService::Request; use Carp 'croak'; use Moo; use namespace::clean; # The required attribute 'ds' is the data service with which this request is # associated. has ds => ( is => 'ro', isa => \&is_dataservice_object, required => 1 ); # The required attribute 'outer' is the request object generated by the # foundation framework. has outer => ( is => 'ro', required => 1 ); # The attribute 'path' selects the data service node which will be used to # process the request. If not given explicitly, it will be retrieved from the # 'outer' request object. has path => ( is => 'rw', trigger => \&_match_node ); # The following argument can be specified separately, or you can let it be # extracted from the value of 'path'. But note that if 'path' does not # correspond to a defined node, then 'rest_path' will be modified # accordingly. has rest_path => ( is => 'ro' ); # The attribute 'http_method' selects the HTTP method which will be used to # process the request. If not given explicitly, it will be retrieved from the # 'outer' request object. has http_method => ( is => 'rw' ); # The following attributes will be determined automatically by # Web::DataService, unless explicitly defined at initialization time or # explicitly overridden later. has is_invalid_request => ( is => 'rw' ); has is_doc_request => ( is => 'rw' ); has output_format => ( is => 'rw' ); has output_vocab => ( is => 'rw' ); has output_linebreak => ( is => 'rw' ); has result_limit => ( is => 'rw' ); has result_offset => ( is => 'rw' ); has display_header => ( is => 'rw' ); #, lazy => 1, builder => sub { $_[0]->_init_value('header') } ); has display_datainfo => ( is => 'rw' ); has display_counts => ( is => 'rw' ); has save_output => ( is => 'rw' ); has save_filename => ( is => 'rw' ); has do_not_stream => ( is => 'rw' ); # The following attributes will be determined automatically by # Web::DataService and should not be overridden. has node_path => ( is => 'ro', init_arg => '_node_path' ); has is_node_path => ( is => 'ro', init_arg => undef ); # BUILD ( ) # # Finish generating a new request object. This involves determining the "node # path" from the "raw path" that was specified when this object was # initialized. Depending upon the features enabled for this data service, the # path may be processed in different ways. sub BUILD { my ($self) = @_; my $ds = $self->{ds}; } sub is_dataservice_object { die "must be an object of class Web::DataService" unless ref $_[0] && $_[0]->isa('Web::DataService'); } sub _init_value { my ($self, $attr) = @_; if ( my $special = $self->special_value($attr) ) { return $special; } elsif ( my $default = $self->{ds}->node_attr("default_$attr") ) { return $default; } else { return; } } # _match_node ( ) # # This routine will be called whenever the 'path' attribute of this request is # set. It determines the closest matching 'node_path' and sets some other # attributes. sub _match_node { my ($self) = @_; local($Carp::CarpLevel) = 1; # We shouldn't have to do this, but # Moo and Carp don't play well together. my $ds = $self->{ds}; my $raw_path = $self->{path}; my $suffix_is_missing; # We start with the raw path and trim it in various ways to find the # closest matching data service node. my $node_path = $raw_path; # If the raw path exactly matches any node, we just use that. Otherwise, # apply any applicable path transformations. if ( exists $ds->{node_attrs}{$raw_path} ) { $self->{is_node_path} = 1; $self->{is_doc_request} = 1 if $ds->has_feature('format_suffix'); } else { # If the feature 'format_suffix' is enabled and the specified path has # a suffix, split it off. if ( $ds->has_feature('format_suffix') ) { if ( $node_path =~ qr{ ^ (.+) [.] (.+) }xs ) { $node_path = $1; $self->output_format($2); } else { $suffix_is_missing = 1; } } # If the feature 'doc_paths' is enabled and the specified path ends in # '_doc', remove that string and set the 'is_doc_request' flag for this # request. Under this feature, the path "abc/def_doc" indicates a # request for doumentation about the path "abc/def". if ( $ds->has_feature('doc_paths') ) { if ( $node_path eq '' ) { $self->{is_doc_request} = 1; } elsif ( ref $ds->{doc_path_regex} eq 'Regexp' && $node_path =~ $ds->{doc_path_regex} ) { $node_path = $1; $self->{is_doc_request} = 1; } elsif ( $ds->{doc_index} && $node_path eq $ds->{doc_index} ) { $node_path = ''; $self->{is_doc_request} = 1; } elsif ( $suffix_is_missing ) { $self->{is_doc_request} = 1; } } # Otherwise, if the special parameter 'document' is enabled and # present then set the 'is_doc_request' flag for this request. elsif ( my $document_param = $ds->special_param('document') ) { my $params = $Web::DataService::FOUNDATION->get_params($self->{outer}, 'query'); $self->{is_doc_request} = 1 if defined $params->{$document_param}; } # We then lop off components as necessary until we get to a node that has # attributes or until we reach the empty string. We set the 'is_node_path' # flag to 0 (unless it has already been set) to indicate that the request # path does not fully match a defined node. while ( $node_path ne '' ) { # If we find a node with attributes, stop here. last if exists $ds->{node_attrs}{$node_path}; # If this is a documentation request and a template exists that # would correspond to the current node path, then create the node # and stop here. if ( $self->{is_doc_request} and my $doc_path = $ds->check_for_template($node_path) ) { $ds->make_doc_node($node_path, $doc_path); last; } # Otherwise, lop off the last path component and try again. $self->{is_node_path} //= 0; if ( $node_path =~ qr{ ^ (.*) / (.*) }xs ) { $node_path = $1; } else { $node_path = ''; } } } # An empty path should always produce documentation. In fact, it should # produce a "main documentation page" for this data service. The data # service author should make sure that this is so. if ( $node_path eq '' ) { $self->{is_doc_request} = 1; } # If the selected node is disabled, set the 'is_invalid_request' attribute. elsif ( $ds->node_attr($node_path, 'disabled') ) { $self->{is_invalid_request} = 1; } # If 'is_node_path' has not yet been set, then we assume it should be 1. $self->{is_node_path} //= 1; # We save all of the characters removed from the raw path as $rest_path, # so that (for example) we can send a requested file. $self->{rest_path} = substr($raw_path, length($node_path)); # If we got an empty path, turn it into the root node path '/'. $self->{node_path} = $node_path eq '' ? '/' : $node_path; # If the node that we got has either the 'file_path' or 'file_dir' # attribute, then mark this request as 'is_file_path'. If it has # 'file_path', then it is invalid unless $rest_path is empty. If it has # 'file_dir', then it is invalid unless $rest_path is NOT empty. if ( $ds->node_attr($self->{node_path}, 'file_dir') ) { $self->{is_file_path} = 1; $self->{is_invalid_request} = 1 unless defined $self->{rest_path} && $self->{rest_path} ne ''; } elsif ( $ds->node_attr($self->{node_path}, 'file_path' ) ) { $self->{is_file_path} = 1; $self->{is_invalid_request} = 1 if defined $self->{rest_path} && $self->{rest_path} ne ''; } } # execute ( ) # # Execute this request, and return the result. sub execute { return $_[0]->{ds}->execute_request($_[0]->{outer}); } # configure ( ) # # Configure this request for execution. sub configure { return $_[0]->{ds}->configure_request($_[0]->{outer}); } # Common methods needed for both execution and documentation # request_url # # Return the raw (unparsed) request URL sub request_url { return $Web::DataService::FOUNDATION->get_request_url($_[0]->{outer}); } # base_url # # Return the base component of the URL for this request, of the form "http://some.host/". sub base_url { return $Web::DataService::FOUNDATION->get_base_url($_[0]->{outer}); } # root_url ( ) # # Return the base url plus the path prefix. sub root_url { return $Web::DataService::FOUNDATION->get_base_url($_[0]) . $_[0]->{ds}{path_prefix}; } # path_prefix ( ) # # Return the path prefix for this request. sub path_prefix { return $_[0]->{ds}{path_prefix}; } # generate_url ( attrs ) # # Generate a URL based on the specified attributes. This routine calls # Web::DataService::generate_url to create a site-relative URL, then applies # the base URL from the current request if asked to do so. sub generate_url { my ($self, $attrs) = @_; return $self->{ds}->generate_site_url($attrs); } # data_info ( ) # # Return a hash of information about the request. sub datainfo { my ($self) = @_; # We start with the information provided by the data service, and add some # info specific to this request. my $ds = $self->{ds}; my $info = $ds->data_info; my $node_path = $self->node_path; my $base_url = $self->base_url; my $doc_url = $self->generate_url({ node => $node_path }); $doc_url =~ s{^/}{}; my $data_url = $self->request_url; $data_url =~ s{^/}{}; $info->{documentation_url} = $base_url . $doc_url if $ds->{feature}{documentation}; $info->{data_url} = $base_url . $data_url; return $info; } sub data_info { goto &datainfo; } sub datainfo_keys { return $_[0]->{ds}->data_info_keys; } sub extra_datainfo_keys { my ($request) = @_; return unless ref $request->{list_datainfo} eq 'ARRAY'; my @keys; my %found; foreach my $k ( @{$request->{list_datainfo}} ) { next if $found{$k}; next unless defined $request->{extra_datainfo}{$k}; push @keys, $k; $found{$k} = 1; } return @keys; } sub extra_datainfo { my ($request, $key) = @_; if ( defined $key ) { return $request->{extra_datainfo}{$key}; } else { return $request->{extra_datainfo}; } } sub extra_datainfo_title { my ($request, $key) = @_; if ( defined $key ) { return $request->{title_datainfo}{$key}; } else { return $request->{title_datainfo}; } } # contact_info ( ) # # Return a hash of information indicating who to contact about this data service. sub contact_info { my ($self) = @_; my $ds = $self->{ds}; return $ds->contact_info; } # node_attr ( ) # # Return the specified attribute of the node that matches this request. sub node_attr { my ($self, $attr) = @_; my $ds = $self->{ds}; my $path = $self->node_path; return $ds->node_attr($path, $attr); } # special_value ( param ) # # Return the value of the specified special param, if it is enabled, and if # it was specified. Return undefined otherwise. sub special_value { my ($self, $param) = @_; my $ds = $self->{ds}; my $param_name = $ds->special_param($param) || return; # If we have already passed the params through HTTP::Validate, return the # cleaned value. if ( ref $self->{valid} ) { my $value = $self->{valid}->value($param_name); return @$value if ref $value eq 'ARRAY'; return $value if defined $value; return; # otherwise } # Otherwise, return the raw value if it exists and undefined otherwise. return $self->{raw_params}{$param_name}; } # special_given ( param ) # # Return true if the given special parameter was specified in the request, # regardless of its value. sub special_given { my ($self, $param) = @_; my $ds = $self->{ds}; my $param_name = $ds->special_param($param) || return; return exists $self->{raw_params}{$param_name}; } # default_limit ( ) # # Return the default result limit, if any. Return undefined otherwise. sub default_limit { my ($self) = @_; my $path = $self->{node_path}; return $self->{ds}->node_attr($path, 'default_limit'); } sub set_scratch { my ($self, $key, $value) = @_; return $self->{ds}->set_scratch($key, $value); } sub get_scratch { my ($self, $key) = @_; return $self->{ds}->get_scratch($key); } sub add_warning { my ($self, $warn_msg) = @_; $self->{warnings} = [] unless ref $self->{warnings} eq 'ARRAY'; push @{$self->{warnings}}, $warn_msg; } sub warnings { my ($self) = @_; return @{$self->{warnings}} if ref $self->{warnings} eq 'ARRAY'; return; } sub errors { my ($self) = @_; return @{$self->{errors}} if ref $self->{errors} eq 'ARRAY'; return; } sub cautions { my ($self) = @_; return {@$self->{cautions}} if ref $self->{cautions} eq 'ARRAY'; return; } sub error_result { my ($self, $error) = @_; return $self->{ds}->error_result($error, $self); } =head1 NAME Web::DataService::Request - object class for data service requests =head1 SYNOPSIS As each incoming request is handled by the Web::DataService framework, a request object is created to represent it. This object is blessed into a subclass of Web::DataService::Request which provides the methods listed here and also includes one or more L with methods that you have written for evaluating the request and doing the appropriate fetching/storing on the backend data system. The simplest way for the main application to handle a request is as follows: Web::DataService->handle_request(request); This call uses the C function provided by the foundation framework (L) to get the "outer" object representing this request from the point of view of the foundation framework, and then passes it to the L method of Web::DataService. In the process of handling the request, a new "inner" request object is generated and blessed into an appropriate subclass of Web::DataService::Request. If you wish more control over this process, you can substitute the following: my $inner = Web::DataService->new_request(request); # You can put code here to check the attributes of $inner and possibly # alter them... $inner->execute; Whichever of these code fragments you use should go into a Dancer L. =head2 Request handling process The request handling process carried out by L works as follows: =over =item 1. The request URL path and parameters are parsed according to the active set of data service features and special parameters. Depending upon the set of data service features that are active, the path may be processed before moving on to the next step. The following is not an exhaustive list, but illustrates the kinds of processing that are done: =over =item * If more than one data service is defined, the appropriate one is selected to handle this request. =item * If the data service has the attribute "path_prefix", this prefix is removed from the path. =item * If the feature 'format_suffix' is active and the path ends in a format suffix such as C<.json>, then that suffix is removed and later used to set the "format" attribute of the request. =item * If the feature 'doc_paths' is active and the path ends in '_doc' or in '/index' then this suffix is removed and the "is_doc_request" attribute of the request is set. =item * If the special parameter 'document' is active and this parameter was included with the request, then the "is_doc_request" attribute of the request is set. =back =item 2. The modified request path is matched against the set of data service node paths. Nodes with either of the attributes C or C match as prefixes, all others must match exactly. If none of the nodes match, then the request is rejected with a HTTP 404 error. Otherwise, a new request object is created and configured using the attributes of the matching node. =back If you used L instead of L, then you get control at this point and can modify the request before calling C<< $request->execute >> to finish the process. =over =item 3. If the matching node has the C attribute, then the contents of the specified file are returned using the foundation framework (i.e. Dancer). If the node has the C attribute instead, then the remainder of the request path is applied as a relative path to that directory. The result of the request will be either the contents of a matching file or a HTTP 404 error. =item 4. Otherwise, the new request will be blessed into an automatically generated subclass of Web::DataService::Request. These subclasses are generated according to the value of the "role" attribute of the matching node. Each different role generates two subclasses, one for documentation requests and one for operation requests. These classes are composed so that they contain the necessary methods for documentation rendering and operation execution, respectively, along with all of the definitions from the primary role (this role module must be written by the application author). =item 5. If the "is_doc_request" attribute has been set, or if the matching node does not have a value for the "method" attribute, then the documentation directory is checked for a template corresponding to the matching node. If not found, the appropriate default template is used. This template is rendered to produce a documentation page, which is returned as the result of the request. =item 6. Otherwise, we can assume that the node has a "method" attribute. The request parameters are then checked against the ruleset named by the "ruleset" attribute, or the ruleset corresponding to the node path if this attribute is not set. =item 7. The output is then configured according to the output blocks(s) indicated by the "output" attribute plus those indicated by "optional_output" in conjunction with the special parameter "show". =item 8. The method specified by the node attribute "method" is then called. This method (which must be written by the application author) is responsible for fetching and/or storing data using the backend system and specifying what the result of the operation should be. =item 9. If the result of the operation is one or more data records, these records are processed according to the specification contained in the selected output block(s), and are then serialized by the module corresponding to the selected output format. If "count" or "datainfo" were requested, or if any warnings or errors were generated during the execution of the request, this material is included in an appropriate way by the serialization module. The resulting response body is returned using the foundation framework. =item 10. If the result of the operation is instead a piece of non-record data (i.e. an image or other binary data), it is returned as the response body using the foundation framework. =back =head1 METHODS =head2 Constructor =head3 new ( attributes... ) You will not need to call this method directly; instead, you should call the L method of Web::DataService, which calls it internally after carrying out other processing. =head2 Execution =head3 execute ( ) Executes this request, and returns the serialized response data. If your main application uses C, then you will not need to call this method because C calls it internally. =head3 error_result ( error ) Aborts processing of the request and causes a HTTP error response to be returned to the client. You will probably not need to call this method, since the preferred way of generating an error condition is to call L. The parameter can be any of the following: =over =item * A HTTP error code, such as "404" or "500". =item * An error message, which will be written to the log. For security reasons, the client will just get back a HTTP 500 response that tells them a server error occurred and they should contact the server administrator. =item * A validation result object from L. The client will get back a HTTP 400 response containing the error and warning messages contained within it. =back =head2 Attribute accessors =head3 ds Returns a reference to the data service instance with which this request is associated. =head3 outer Returns a reference to the "outer" request object defined by the foundation framework. =head3 path Returns the raw path associated with the request. =head3 node_path Returns the path of the node that matches this request. =head3 rest_path If the node path matches the processed request path as a prefix, this accessor returns the remainder of the path. Otherwise, it returns C. =head3 is_invalid_request Returns true if this request could not be matched to a node, or is otherwise invalid. Returns false otherwise. =head3 is_doc_request Returns true if this request has been determined to be a request for documentation, according to the features and special parameters enabled for this data service. Returns false otherwise. =head3 is_node_path Returns true if the request path I matches the path of a data service node, false otherwise. =head3 http_method Returns (or sets) the HTTP method associated with the request. =head3 request_url Returns the complete request URL. =head3 base_url Returns the base component of the request URL, of the form "http://some.host/" or something similar. =head3 root_url Returns the base URL plus the path prefix (if any) for this data service. =head3 path_prefix Returns the path prefix (if any) for this data service. =head3 data_info Returns a hash containing information about the request and the data to be returned. The hash keys will include some or all of the following, depending upon the configuration of the data service. =over =item title, data_provider, data_source, data_license, license_url The values of these keys will be the corresponding attributes of the data service. They may be undefined if no value was specified for them either when the data service was instantiated or in the configuration file maintained by the foundation framework. =item access_time The current time. =item data_url The complete request URL. =item documentation_url A URL that will return a documentation page describing the selected data service node. =back =head3 data_info_keys Returns a list of the keys documented for C, in a canonical order for use by the serialization modules. =head3 contact_info Returns a hash whose keys are C and C. The values will be the values of the data service attributes C and C, respectively. =head3 generate_url ( attrs ) This method returns a URL generated from the specified attributes, which must be given either as a hashref or as a string: # either of the following: $url = $request->generate_url( { op => 'abc/def', format => 'json', params => 'foo=1' } ); $url = $request->generate_url( "op:abc/def.json?foo=1" ); The attributes are as follows: =over =item node Generate a URL which will request documentation using the node path given by the value of this attribute. In the string form, this is represented by a prefix of "node:". =item op Generate a URL which will request a data service operation using the node path given by the value of this attribute. In the string form, this is represented by a prefix of "op:". =item path Generate a URL which will request the exact path given. This is used to generate URLs for requesting files, such as CSS files. In the string form, this is represented by a prefix of "path:". =item format The generated URL will request output in the specified format. Depending upon the features enabled for this data service, that may mean either adding the specified value as a suffix to the URL path, or adding a special parameter. In the string form, this is represented by adding '.' followed by the format to the path (this syntax is fixed no matter which data service features are enabled). =item params Configure the URL to include the specified parameters. The value of this attribute must be either an arrayref with an even number of elements or a single URL parameter string such as C<"foo=1&bar=2">. In the string form, this is represented by a '?' followed by the parameter string. =item type The value of this attribute must be either C, C, or C. If "abs" is given, then the generated URL will begin with "http[s]://hostname[:port]/. If "site", then the generated URL will begin with "/" followed by the path prefix for this data service (if any). If "relative", no prefix will be added to the generated URL. In this case, you must make sure to specify a path that will work properly relative to the URL of the page on which you intend to display it. If this attribute is not specified, it defaults to "site". In the string form, this is represented by modifying the prefix, i.e. "oprel:" or "opabs:" instead of just "op:". =back =head3 node_attr ( name ) Returns the value of the specified attribute of the node matching this request, or I if the attribute is not defined for that node. =head3 special_value ( param ) Returns the value of the specified special parameter, if it is enabled for this data service and if it was included with this request. Returns I otherwise. =head3 special_given ( param ) Returns true if the specified special parameter was included in this request and is enabled for this data service, regardless of its value. Returns false otherwise. =head3 default_limit Returns the default result limit, if any. This is used in generating documentation. =head2 Documentation methods The following methods are only available with documentation requests. They can be called from documentation templates as follows: <% result = request.method_name(args) %> or, to include the result directly in the rendered output: <% request.method_name(args) %> In either case, "request" is a variable automatically provided to the templating engine. The string "result" should be replaced by whatever variable you wish to assign the result to, and "method_name" and "args" by the desired method and arguments. These methods are all packaged up in blocks defined by doc_defs.tt, so you will not need to call them directly unless you want to make substantive changes to the look of the documentation pages. =head3 document_node Returns the documentation string for the node matching this request, or the empty string if none was defined. =head3 list_navtrail Returns a list of navigation trail components for the current request, in Pod format. These are generated componentwise from the node path. =head3 document_usage Returns a string in Pod format listing the URLs generated from the node attribute "usage", if any were given for this node. =head3 list_subnodes Returns a list of the node paths corresponding to all subnodes of the current one for which the C attribute was set. =head3 document_subnodes Returns a string in Pod format documenting the subnodes of the current one for which the C attribute was set. =head3 document_params Returns a string in Pod format documenting the parameters available for this request. This is automatically generated from the corresponding ruleset, if one was defined. =head3 list_http_methods Returns a list of HTTP methods that are allowed for this request path. =head3 document_http_methods Returns a string in Pod format documenting the HTTP methods that are allowed for this request path. =head3 document_response Returns a string in Pod format documenting the all of the fields that might be included in the result. =head3 output_label Returns the value of the node attribute "output_label", or the default value "basic" if none was defined. =head3 optional_output Returns the value of the node attribute "optional_output", if this attribute was defined. =head3 document_formats ( options ) Returns a string in Pod format documenting the formats allowed for this node. If a parameter is specified, it must be a hashref. If this includes the key 'all' with a true value, then all formats defined for this data service are included. If it includes the key 'extended' with a true value, then a description of each format is included. =head3 default_format Return the name of the default format (if any was specified) for this node. =head3 document_vocabs ( options ) Return a string in Pod format documenting the vocabularies allowed for this node. If a parameter is specified, it must be a hashref. If this includes the key 'all' with a true value, then all vocabularies defined for this data service are included. If it includes the key 'extended' with a true value, then a description of each vocabulary is included. =head3 output_format You can use this method to check whether the response is to be rendered into HTML or returned as Pod text. The values returned are C and respectively. =head2 Operation methods The following methods are only available with operation requests. They can be called from any of the operation methods included in the application role module(s). =head3 get_connection This method can only be used if you explicitly specified a backend plugin when instantiating the data service, or if the module L was required in your main application before Web::DataService. Assuming that the proper connection information is present in the application configuration file, this method will return a connection to your backend data store. =head3 exception ( code, message ) Returns an exception object encoding the specified HTTP result code and a message to go with it. This exception object can be used as an argument to 'die'. Its type is C. =head3 debug Returns true if the data service process is running under debug mode. This can be used to output extra debugging information as appropriate. =head3 debug_line ( text ) Print the specified text to standard error, followed by a newline, if debug mode is enabled for this data service process. This can be used, for example, to output the text of SQL statements executed on the underlying database, and similar information, for debugging purposes. For example: $request->debug_line($sql) if $request->debug; =head3 param_keys ( ) Returns a list of the parameter keys corresponding to the request parameters. These will generally be the same as the parameter names, but may be different if you include the keys C and/or C in the parameter validation rulesets (see L). =head3 clean_param ( param ) Returns the cleaned value of the specified parameter, or undefined if the parameter was not included in the request. If more than one parameter value was given, the result will be an array ref. =head3 clean_param_list ( param ) Returns a list of all the cleaned values of the specified parameter (one or more), or empty if the parameter was not included in the request. =head3 clean_param_hash ( param ) Returns a hashref whose keys are all of the cleaned values of the specified parameter, or an empty hashref if the parameter was not included in the request. =head3 param_given ( param ) Returns true if the specified parameter was included in there request, whether or not it had a valid value. Returns false otherwise. =head3 params_for_display Returns a list of (parameter, value) pairs for use in responding to the 'datainfo' special parameter. This result list leaves out any special parameters that do not affect the content of the result. Multiple values are concatenated together using commas. =head3 validate_params ( ruleset_name, param... ) Validates the specified parameters (which may be hash or list refs of parameters and values) to the specified ruleset. Returns the result, which will be an object of type HTTP::Validate::Result that can then be queried for success or failure, error messages, etc. The primary purpose for this method is to allow validation of data records before they are added to the underlying database. =head3 raw_body Returns the request body as an un-decoded string. If the request does not contain a body, returns the empty string. The primary purpose for this method is to accept data for addition to the underlying database. =head3 decode_body Attempts to decode the request body, if it is in a known format. Currently, the only two formats understood are JSON and text. A JSON body will be decoded into a Perl data structure, while a text body will be split into a list of lines. If no body exists, the undefined value is returned. If an error occurs during JSON decoding, it will be returned as a second item. Consequently, the return value of this method should be assigned to a two-element list. =head3 has_block ( block ) Returns true if the specified output block was selected for this request. The parameter can be either the name of the block or the parameter value by which it would be selected. =head3 select_list ( subst ) Returns a list of strings derived from the 'select' configuration records found in the output blocks selected for this request. You can use these records to specify which fields need to be retrieved from the backend data store. Any given string will only appear once in the list, even if it occurs in multiple 'select' records. The optional argument should be a hashref, indicating substitutions to be made. For example, given the following code, $ds->define_block( 'block1' => { select => '$a.name', '$a.value', 'c.size' }, ... ); # ...and in a different subroutine... my @fields = $request->select_list( { a => 'table1', b => 'table2' } ) The result will include C, C, and C. If you are using an SQL-based backend, and if you set up the 'select' records properly, you can use this method (or see C below) to build a SELECT statement that will fetch exactly the information needed for the selected set of output blocks. This is important if your application allows optional output blocks that can be selected arbitrarily by the client. If you are using a non-SQL backend, you are free to use this in any way that makes sense given the backend interface. =head3 select_hash ( subst ) Returns the same set of strings as C, but as the keys in a hash. The values will all be 1. =head3 select_string ( subst ) Returns the same set of strings as C, but joined into a single string with each item separated by a comma and a space. =head3 substitute_select ( variable => value, variable => value ... ) Make substitutions in the select list. For example, if passed the arguments 'v => xt', then anywhere the string '$v' appears in the select list, that string will be replaced by 'xt'. This method should be called before 'select_list' or 'select_string'. The intended use is to select between one or more alternate database tables. =head3 tables_hash Returns a hashref whose keys are derived from the 'select' configuration records found in the output blocks selected for this request. The keys from this hash will be the values specified by any 'tables' attributes in those configuration records. The values will all be 1. If you are using an SQL-based backend, and if you set up the 'select' records properly, you can use this method to keep track of which database tables will be needed in order to build a query that can satisfy all of the data blocks included in this request. You can use the set of keys to constuct an appropriate list of table joins. If you are using a non-SQL backend, you can use this in any way that makes sense given the backend interface. If you call this method multiple times, you will get a reference to the same hash each time. This means that you can safely add and remove keys during your own processing. =head3 add_table ( alias, [full_name] ) Add an extra key to the tables hash. The optional second parameter can be the full name of the table to be included, but this is ignored. Note that adding a table to the table hash will I automatically include that table in any SQL statements. You need to check the table hash and add it yourself if the appropriate key is found. =head3 filter_hash Returns a hashref whose keys are derived from the 'filter' configuration records found in the output blocks selected for this request. The values will all be 1. If you are using an SQL-based backend, and if you set up the 'filter' records properly, you can use this method to get a list of (unique) filter expressions to use in building a query that can satisfy all of the data blocks included in this request. (Note: you will almost always need to add other filter expressions derived from parameter values as well). If you are using a non-SQL backend, you can use this in any way that makes sense given the backend interface. If you call this method multiple times, you will get a reference to the same hash each time. This means that you can safely add and remove keys during your own processing. =head3 output_field_list This method returns the output field list for the current request. This is the actual list, not a copy, so it can be manipulated. This is a real hack, which will probably be removed from a future version of this module. But for the time being, you can use it (for example) to go through the list and prune fields that will not be used. =head3 delete_output_field ( field ) Remove the specified output field from the list. The first field whose name (not label) matches the argument is removed, regardless of what output block it occurs in. If no fields match, nothing will be removed. =head3 result_limit Returns the result limit specified for this request, or undefined if none was given or if C was specified. Even though Web::DataService will always truncate the result set if a limit was given, you may want to include this limit value in any queries you make to the backend so as to prevent your server from doing unnecessary work. =head3 result_offset ( will_handle ) Returns true if a result offset was specified for this request, or zero if none was specified. If the argument value is true, then Web::DataService assumes that you will handle the process of offsetting the data result, e.g. by using modifying your backend query appropriately (see C below). If the argument is false, or if you never call either this method or C, then (if an offset was specified for this request) Web::DataService will automatically discard the corresponding number of records from the beginning of the result set before serializing the results. =head3 sql_limit_clause ( will_handle ) Returns a string whose value is an LIMIT clause that can be added to an SQL query to generate a result set in accordance with result limit and offset (if any) specified for this request. If the argument is true, then Web::DataService assumes that you will actually do this. If the argument is false, or if you never call either this method or C, then (if an offset was specified for this request) Web::DataService will automatically discard the corresponding number of records from the beginning of the result set before serializing the results. If a result limit was specified for this request, and if the result set you generate exceeds this size, Web::DataService will always truncate it to match the specified limit. =head3 sql_count_clause ( ) Returns a string that can be added to an SQL statement to generate a result count in accordance with the request parameters. If the special parameter "count" was specified for this request, the result will be C. Otherwise, it will be the empty string. If you are using a non-SQL backend, you can still use this as a boolean variable to determine if the result should include a count of the number of records found. =head3 sql_count_rows ( ) Execute the SQL statement "SELECT FOUND ROWS" and store the result for use in generating the response header. But only do this if the request parameters specify that result counts should be returned. =head3 set_result_count ( count ) Use this method to tell Web::DataService the result count if you are using C. In this case, you will generally need to execute a separate query such as "SELECT FOUND_ROWS()" after your main query. =head3 output_format Returns the name of the response format selected for this request. =head3 output_vocab Returns the name of the response vocabulary selected for this request. If no vocabularies have been defined for this data service, it will return "null", the name of the default vocabulary. =head3 output_linebreak Returns the string to be inserted between output lines in text-based response formats. This will be either a carriage return, a linefeed, or both. =head3 result_limit Returns the limit (if any) specified for the size of the result set. =head3 result_offset Returns the offset (if any) specified for the start of the result set. =head3 display_header Returns true if header material is to be displayed in text-based response formats, false otherwise. =head3 display_datainfo Returns true if a description of the dataset is to be included in the response, false otherwise. =head3 display_counts Returns true if result counts and elapsed time are to be included in the response, false otherwise. =head3 save_output Returns true if the special parameter 'save' was included in the request. =head3 get_config Returns a hashref providing access to the attributes defined in the application configuration file. Allows you to include application-specific directives in the file and retrieve them as needed from your operation methods. =head3 set_content_type Specify the value for the "Content-type" HTTP response header. You will probably not need to call this, since it is set automatically based on the selected response format. =h3ad3 exception ( code, message ) Returns an exception object which can be used as an argument to C. This will abort processing of the current request and generate an HTTP error result. The first argument must be a valid HTTP error code. =head2 Result methods These methods are also available with operation requests. I These methods are how you tell Web::DataService the result of each operation. You can make more than one of these calls from a single operation, but note that each call (except for C in some cases) wipes out any result specified by previous calls. =head3 single_result ( record ) The argument must be a hashref representing a single data record. The result of this operation will be that single record. =head3 list_result ( record... ) You can call this method either with a single arrayref argument whose members are hashrefs, or with a list of hashrefs. Either way, the result of this operation will be the specified list of records. =head3 add_result ( record... ) Adds the specified record(s) to a result set previously specified by C. All arguments must be hashrefs. =head3 sth_result ( sth ) The argument must be a valid DBI statement handle that has already been executed. Once your operation method returns, Web::DataService will then fetch the result records from this sth one by one, process them according to the selected set of output blocks for this request, and serialize the result. If you use this result method, you will need to call either C or C to determine if a result count is needed. If so, execute a "SELECT FOUND_ROWS()" query (or the equivalent) and use C to tell Web::DataService how many records were found. =head3 data_result ( data ) The argument must be either a scalar or a reference to a scalar. In either case, this data will be returned as the result of the operation without any further processing. You can use this, for example, to return images or other binary data. You can use C to set the result content type, or you can rely on the suffix of the URL path if appropriate. =head3 clear_result This call clears any results that have been specified for this operation by any of the other methods in this section. =head3 skip_output_record ( record ) The given record is marked in such a way as to cause it to be omitted from the output. The record counts are not updated, however, which limits its utility. This method can be called from an operation method if C is being used, or else from a C routine. =head3 select_output_block ( record, block_name ) The given record will be output using the specified output block, instead of the output block specified by the operation node attributes. That output block will be configured if it has not yet been configured for this operation. Optional output blocks are not affected. A future version of this module will provide ways to alter the optional output blocks. =head3 add_warning ( message ) Add the specified warning message to this request. The warnings will be automatically included in the result, in a manner appropriate for the selected response format. =head3 warnings ( ) Return a list of the warning messages (if any) that have been added to this request. =head3 add_caution ( message ) Add the specified caution message to this request. The caution messages will be automatically included in the result, in a manner appropriate for the selected response format. =head3 cautions ( ) Return a list of the caution messages (if any) that have been added to this request. =head3 add_error ( message ) Add the specified error message to this request. The error messages will be automatically included in the result, in a manner appropriate for the selected response format. =head3 errors ( ) Return a list of the error messages (if any) that have been added to this request. =head1 AUTHOR mmcclenn "at" cpan.org =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 COPYRIGHT & LICENSE Copyright 2014 Michael McClennen, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1;