From a86ee3a1ca5213d5401c6425cfb0fcfc7bb15e78 Mon Sep 17 00:00:00 2001 From: "mkanat%bugzilla.org" <> Date: Tue, 31 Mar 2009 06:37:53 +0000 Subject: Bug 432907: Create a JSON frontend for WebServices Patch by Max Kanat-Alexander r=dkl, a=mkanat --- Bugzilla/WebService/Bug.pm | 5 + Bugzilla/WebService/Constants.pm | 1 - Bugzilla/WebService/README | 18 +++ Bugzilla/WebService/Server/JSONRPC.pm | 266 ++++++++++++++++++++++++++++++++++ Bugzilla/WebService/Server/XMLRPC.pm | 53 +++++++ 5 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 Bugzilla/WebService/README create mode 100644 Bugzilla/WebService/Server/JSONRPC.pm (limited to 'Bugzilla/WebService') diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index f76d800fa..8ebad41d1 100755 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -56,6 +56,11 @@ use constant FIELD_MAP => { use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component); +use constant DATE_FIELDS => { + comments => ['new_since'], + search => ['last_change_time', 'creation_time'], +}; + ###################################################### # Add aliases here for old method name compatibility # ###################################################### diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index 172d757ef..83b4197aa 100755 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -133,5 +133,4 @@ sub WS_DISPATCH { return $dispatch; }; - 1; diff --git a/Bugzilla/WebService/README b/Bugzilla/WebService/README new file mode 100644 index 000000000..bbe320979 --- /dev/null +++ b/Bugzilla/WebService/README @@ -0,0 +1,18 @@ +The class structure of these files is a little strange, and this README +explains it. + +Our goal is to make JSON::RPC and XMLRPC::Lite both work with the same code. +(That is, we want to have one WebService API, and have two frontends for it.) + +The problem is that these both pass different things for $self to WebService +methods. + +When XMLRPC::Lite calls a method, $self is the name of the *class* the +method is in. For example, if we call Bugzilla.version(), the first argument +is Bugzilla::WebService::Bugzilla. So in order to have $self +(our first argument) act correctly in XML-RPC, we make all WebService +classes use base qw(Bugzilla::WebService). + +When JSON::RPC calls a method, $self is the JSON-RPC *server object*. In other +words, it's an instance of Bugzilla::WebService::Server::JSONRPC. So we have +Bugzilla::WebService::Server::JSONRPC inherit from Bugzilla::WebService. diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm new file mode 100644 index 000000000..b453c6196 --- /dev/null +++ b/Bugzilla/WebService/Server/JSONRPC.pm @@ -0,0 +1,266 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla JSON Webservices Interface. +# +# The Initial Developer of the Original Code is the San Jose State +# University Foundation. Portions created by the Initial Developer +# are Copyright (C) 2008 the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Max Kanat-Alexander + +package Bugzilla::WebService::Server::JSONRPC; + +use strict; +use base qw(JSON::RPC::Server::CGI Bugzilla::WebService::Server); + +use Bugzilla::Error; +use Bugzilla::WebService::Constants; +use Date::Parse; +use DateTime; + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + Bugzilla->_json_server($self); + $self->dispatch(WS_DISPATCH); + $self->return_die_message(1); + $self->json->allow_blessed(1); + $self->json->convert_blessed(1); + # Default to JSON-RPC 1.0 + $self->version(0); + return $self; +} + +# Override the JSON::RPC method to return our CGI object instead of theirs. +sub cgi { return Bugzilla->cgi; } + +sub type { + my ($self, $type, $value) = @_; + + # This is the only type that does something special with undef. + if ($type eq 'boolean') { + return $value ? JSON::true : JSON::false; + } + + return JSON::null if !defined $value; + + my $retval = $value; + + if ($type eq 'int') { + $retval = int($value); + } + if ($type eq 'double') { + $retval = 0.0 + $value; + } + elsif ($type eq 'string') { + # Forces string context, so that JSON will make it a string. + $retval = "$value"; + } + elsif ($type eq 'dateTime') { + # str2time uses Time::Local internally, so I believe it should + # always return seconds based on the *Unix* epoch, even if the + # system doesn't use the Unix epoch. + $retval = str2time($value) * 1000; + } + # XXX Will have to implement base64 if Bugzilla starts using it. + + return $retval; +} + +################## +# Login Handling # +################## + +# This handles dispatching our calls to the appropriate class based on +# the name of the method. +sub _find_procedure { + my $self = shift; + + # This is also a good place to deny GET requests, since we can + # safely call ThrowUserError at this point. + if ($self->request->method ne 'POST') { + ThrowUserError('json_rpc_post_only'); + } + + my $method = shift; + $self->{_bz_method_name} = $method; + + # This tricks SUPER::_find_procedure into finding the right class. + $method =~ /^(\S+)\.(\S+)$/; + $self->path_info($1); + unshift(@_, $2); + + return $self->SUPER::_find_procedure(@_); +} + +# This is a hacky way to do something right before methods are called. +# This is the last thing that JSON::RPC::Server::_handle calls right before +# the method is actually called. +sub _argument_type_check { + my $self = shift; + my $params = $self->SUPER::_argument_type_check(@_); + + # This is the best time to do login checks. + $self->handle_login(); + + # If there are no parameters, we don't need to parse them. + return $params if !ref $params; + + # JSON-RPC 1.0 requires all parameters to be passed as an array, so + # we just pull out the first item and assume it's an object. + if (ref $params eq 'ARRAY') { + $params = $params->[0]; + } + + # Now, convert dateTime fields on input. + $self->_bz_method_name =~ /^(\S+)\.(\S+)$/; + my ($class, $method) = ($1, $2); + my $pkg = $self->{dispatch_path}->{$class}; + my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] }; + foreach my $field (@date_fields) { + if (defined $params->{$field}) { + my $value = $params->{$field}; + if (ref $value eq 'ARRAY') { + $params->{$field} = + [ map { $self->_bz_convert_datetime($_) } @$value ]; + } + else { + $params->{$field} = $self->_bz_convert_datetime($value); + } + } + } + + # Bugzilla::WebService packages call internal methods like + # $self->_some_private_method. So we have to inherit from + # that class as well as this Server class. + my $new_class = ref($self) . '::' . $pkg; + my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)"; + eval "package $new_class;$isa_string;"; + bless $self, $new_class; + + return $params; +} + +sub _bz_convert_datetime { + my ($self, $time) = @_; + my $dt = DateTime->from_epoch(epoch => ($time / 1000), + time_zone => Bugzilla->local_timezone); + return $dt->strftime('%Y-%m-%d %T'); +} + +sub handle_login { + my $self = shift; + + my $path = $self->path_info; + my $class = $self->{dispatch_path}->{$path}; + my $full_method = $self->_bz_method_name; + $full_method =~ /^\S+\.(\S+)/; + my $method = $1; + $self->SUPER::handle_login($class, $method, $full_method); +} + +# _bz_method_name is stored by _find_procedure for later use. +sub _bz_method_name { + return $_[0]->{_bz_method_name}; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::WebService::Server::JSONRPC - The JSON-RPC Interface to Bugzilla + +=head1 DESCRIPTION + +This documentation describes things about the Bugzilla WebService that +are specific to JSON-RPC. For a general overview of the Bugzilla WebServices, +see L. + +Please note that I about this JSON-RPC interface is +B. If you want a stable API, please use the +C interface. + +=head1 JSON-RPC + +Bugzilla supports both JSON-RPC 1.0 and 1.1. We recommend that you use +JSON-RPC 1.0 instead of 1.1, though, because 1.1 is deprecated. + +At some point in the future, Bugzilla may also support JSON-RPC 2.0. + +The JSON-RPC standards are described at L. + +=head1 CONNECTING + +The endpoint for the JSON-RPC interface is the C script in +your Bugzilla installation. For example, if your Bugzilla is at +C, then your JSON-RPC client would access the +API via: C + +Bugzilla only allows JSON-RPC requests over C. C requests +(or any other type of request, such as C) will be denied. + +=head1 PARAMETERS + +For JSON-RPC 1.0, the very first parameter should be an object containing +the named parameters. For example, if you were passing two named parameters, +one called C and the other called C, the C element of +your JSON-RPC call would look like: + + "params": [{ "foo": 1, "bar": "something" }] + +For JSON-RPC 1.1, you can pass parameters either in the above fashion +or using the standard named-parameters mechanism of JSON-RPC 1.1. + +C fields are represented as an integer number of microseconds +since the Unix epoch (January 1, 1970 UTC). They are always in the UTC +timezone. + +All other types are standard JSON types. + +=head1 ERRORS + +All errors thrown by Bugzilla itself have 100000 added to their numeric +code. So, if the documentation says that an error is C<302>, then +it will be C<100302> when it is thrown via JSON-RPC. + +Errors less than 100000 are errors thrown by the JSON-RPC library that +Bugzilla uses, not by Bugzilla. + +=head1 SEE ALSO + +=head2 Server Types + +=over + +=item L + +=item L + +=back + +=head2 WebService Methods + +=over + +=item L + +=item L + +=item L + +=item L + +=back diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm index 36b4e01fd..5c92532a9 100644 --- a/Bugzilla/WebService/Server/XMLRPC.pm +++ b/Bugzilla/WebService/Server/XMLRPC.pm @@ -168,3 +168,56 @@ sub as_string { } 1; + +__END__ + +=head1 NAME + +Bugzilla::WebService::Server::XMLRPC - The XML-RPC Interface to Bugzilla + +=head1 DESCRIPTION + +This documentation describes things about the Bugzilla WebService that +are specific to XML-RPC. For a general overview of the Bugzilla WebServices, +see L. + +=head1 XML-RPC + +The XML-RPC standard is described here: L + +=head1 CONNECTING + +The endpoint for the XML-RPC interface is the C script in +your Bugzilla installation. For example, if your Bugzilla is at +C, then your XML-RPC client would access the +API via: C + +=head1 PARAMETERS + +C fields are the standard C XML-RPC field. They +should be in C format (where C is a literal T). + +All other fields are standard XML-RPC types. + +=head2 How XML-RPC WebService Methods Take Parameters + +All functions take a single argument, a C<< >> that contains all parameters. +The names of the parameters listed in the API docs for each function are the +C<< >> element for the struct C<< >>s. + +=head1 EXTENSIONS TO THE XML-RPC STANDARD + +=head2 Undefined Values + +Normally, XML-RPC does not allow empty values for C, C, or +C fields. Bugzilla does--it treats empty values as +C (called C or C in some programming languages). + +Bugzilla also accepts a type called C<< >>, which is always considered +to be C, no matter what it contains. + +=begin private + +nil is implemented by XMLRPC::Lite, in XMLRPC::Deserializer::decode_value. + +=end private \ No newline at end of file -- cgit v1.2.3-24-g4f1b