[lux commit] r124 - in trunk/Sungrazr: . Api Controller Controller/Server Controller/Server/Adapter

0 views
Skip to first unread message

codesite...@google.com

unread,
Mar 10, 2008, 4:37:04 AM3/10/08
to lux...@googlegroups.com
Author: rodrigo.moraes
Date: Mon Mar 10 01:36:17 2008
New Revision: 124

Added:
trunk/Sungrazr/
trunk/Sungrazr/Api/
trunk/Sungrazr/Api/JsonRpc.php (contents, props changed)
trunk/Sungrazr/Api/TestJsonRpc.php (contents, props changed)
trunk/Sungrazr/Controller/
trunk/Sungrazr/Controller/Server/
trunk/Sungrazr/Controller/Server.php (contents, props changed)
trunk/Sungrazr/Controller/Server/Adapter/
trunk/Sungrazr/Controller/Server/Adapter.php (contents, props changed)
trunk/Sungrazr/Controller/Server/Adapter/JsonRpc.php (contents,
props changed)
trunk/Sungrazr/Controller/Server/Api.php (contents, props changed)
trunk/Sungrazr/Controller/Service.php (contents, props changed)

Log:
Adding Sungrazr_Server series refactored.

Added: trunk/Sungrazr/Api/JsonRpc.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Api/JsonRpc.php Mon Mar 10 01:36:17 2008
@@ -0,0 +1,39 @@
+<?php
+/**
+ *
+ * An example app for a JSON-RPC service.
+ *
+ */
+class Sungrazr_Api_JsonRpc extends Sungrazr_Controller_Service
+{
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Config keys are ...
+ *
+ * `server`
+ * : (dependency) Server config
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Api_JsonRpc = array(
+ 'server' => array(
+ 'adapter' => 'Sungrazr_Controller_Server_Adapter_JsonRpc',
+ ),
+ );
+
+ /**
+ *
+ * Description test. Narrative test.
+ *
+ * @param int $apikey The authentication key.
+ *
+ * @param array $properties Properties for the new record.
+ *
+ */
+ public function create($apikey, $properties)
+ {
+ }
+}
\ No newline at end of file

Added: trunk/Sungrazr/Api/TestJsonRpc.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Api/TestJsonRpc.php Mon Mar 10 01:36:17 2008
@@ -0,0 +1,66 @@
+<?php
+/**
+ *
+ * An example *client* for a JSON-RPC service.
+ *
+ */
+class Sungrazr_Api_TestJsonRpc extends Solar_Controller_Page
+{
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Config keys are ...
+ *
+ * `uri`
+ * : (string) Service URI.
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Api_TestJsonRpc = array(
+ 'uri' => null,
+ );
+
+ /**
+ *
+ * The default page controller action.
+ *
+ * @var string
+ *
+ */
+ protected $_action_default = 'test';
+
+ public function actionTest()
+ {
+ $uri = $this->_config['uri'] . '/json-rpc';
+
+ $call = array(
+ 'method' => 'stuff.create',
+ 'params' => array(
+ array('apikey' => 'foo'),
+ array('properties' => array(
+ 'first_name' => 'John',
+ 'last_name' => 'Public',
+ )),
+ ),
+ 'id' => 42,
+ );
+
+ $call = Solar::factory('Solar_Json')->encode($call);
+
+ $request = Solar::factory('Solar_Http_Request');
+ $response = $request->setUri($uri)
+ ->setMethod('post')
+ ->setContentType('application/json')
+ ->setContent($call)
+ ->fetch();
+
+ $code = $response->getStatusCode();
+ $msg = $response->content;
+
+ Solar::dump($code);
+ Solar::dump($msg);
+ die();
+ }
+}

Added: trunk/Sungrazr/Controller/Server.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Controller/Server.php Mon Mar 10 01:36:17 2008
@@ -0,0 +1,66 @@
+<?php
+/**
+ *
+ * Factory class for server adapters.
+ *
+ * Heavily modified and refactored from Zend_Server and related classes.
+ *
+ * Developed for, and then donated by, Mashery.com <http://mashery.com>.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server
+ *
+ * @author Clay Loveless <cl...@killersoft.com>
+ *
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ *
+ * @version SVN: $Id$
+ *
+ */
+
+/**
+ *
+ * Factory class for server adapters.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server
+ *
+ */
+class Sungrazr_Controller_Server extends Solar_Base {
+
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Keys are ...
+ *
+ * `adapter`
+ * : (string) The adapter class for the factory, default
+ * 'Sungrazr_Controller_Server_Adapter_Rest'.
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Controller_Server = array(
+ 'adapter' => 'Sungrazr_Controller_Server_Adapter_Rest',
+ );
+
+ /**
+ *
+ * Factory method to create server adapter objects.
+ *
+ * @return Sungrazr_Controller_Server_Adapter
+ *
+ */
+ public function solarFactory()
+ {
+ // bring in the config and get the adapter class.
+ $config = $this->_config;
+ $class = $config['adapter'];
+ unset($config['adapter']);
+
+ return Solar::factory($class, $config);
+ }
+}

Added: trunk/Sungrazr/Controller/Server/Adapter.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Controller/Server/Adapter.php Mon Mar 10 01:36:17 2008
@@ -0,0 +1,423 @@
+<?php
+/**
+ *
+ * Abstract server adapter.
+ *
+ * Heavily modified and refactored from Zend_Server and related classes.
+ *
+ * Developed for, and then donated by, Mashery.com <http://mashery.com>.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server_Adapter
+ *
+ * @author Clay Loveless <cl...@killersoft.com>
+ *
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ *
+ * @version SVN: $Id$
+ *
+ */
+
+/**
+ *
+ * Abstract server adapter.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server_Adapter
+ *
+ */
+abstract class Sungrazr_Controller_Server_Adapter
+ extends Solar_Base implements Serializable {
+
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Config keys are ...
+ *
+ * `return`
+ * : (bool) Whether or not to return a response. Defaults to **FALSE**.
+ * When **TRUE**, the fetch() method will not send output but will
+ * instead return the response that would otherwise have been sent.
+ *
+ * `headers`
+ * : (array) Associative array of headers to return with every response.
+ *
+ * `content_types`
+ * : (array) List of acceptable content types for requests and responses.
+ * The Content-Type of the inbound request is checked against the
+ * 'request' list prior to unserializing the inbound request. The Accept
+ * header of the request is examined for preferred types of supported
+ * responses in the serialization of the response. If Accept header
+ * is omitted or indicates a wildcard, the first supported response
+ * type is used.
+ *
+ * `server_request`
+ * : (array) Parameters for the request object dependency used
within the
+ * Adapter This bypasses the Solar_Request dependency that Solar
sets up
+ * by default to allow for extended request objects easily. The first
+ * element of the array is the class name to pass to [[Solar::dependency]],
+ * and the second is the second parameter to pass to Solar::dependency.
+ *
+ * `send_length`
+ * : (bool) Send the content-length of the response. If the web server
+ * is performing automatic compression after the output of the PHP script,
+ * you may wish to turn this off so the connection doesn't hang
due to
+ * a mismatch between the advertised size of the Content-Length
and the
+ * actual size of the compressed response.
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Controller_Server_Adapter = array(
+ 'classes' => null,
+ 'return' => false,
+ 'headers' => array(),
+ 'content_types' => array(
+ 'request' => array('text/xml'),
+ 'response' => array('text/xml'),
+ ),
+ 'server_request' => array(
+ 'class' => 'Solar_Request',
+ 'spec' => null,
+ ),
+ 'send_length' => true,
+ 'api' => null,
+ );
+
+ /**
+ *
+ * Sungrazr_Controller_Server_Api object handle.
+ *
+ * @var Sungrazr_Controller_Server_Api
+ *
+ */
+ protected $_api;
+
+ /**
+ *
+ * HTTP headers to send with the response.
+ *
+ * @var array
+ *
+ */
+ protected $_headers = array();
+
+ /**
+ *
+ * Solar_Request-based dependency.
+ *
+ * @var Solar_Request
+ *
+ */
+ protected $_request;
+
+ /**
+ *
+ * Array of introspective API information, indexed by callable method
+ * names instead of internal class names.
+ *
+ * @var array
+ *
+ */
+ protected $_server_api = array();
+
+ /**
+ *
+ * Special introspective methods provided by the server.
+ *
+ * @var array
+ *
+ */
+ protected $_server_specials = array();
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param array $config User-provided configuration values.
+ *
+ */
+ public function __construct($config = null)
+ {
+ // basic construction
+ parent::__construct($config);
+
+ // pick up config options
+ $this->_headers = array_merge($this->_headers,
+ $this->_config['headers']);
+
+ // set up request dependency
+ if (! Solar_Registry::exists('server_request')) {
+ Solar_Registry::set(
+ 'server_request',
+ $this->_config['server_request']['class'],
+ $this->_config['server_request']['spec']
+ );
+ }
+ $this->_request = Solar_Registry::get('server_request');
+
+ // make extra-sure that no headers are output prematurely
+ ob_start();
+
+ // API object to perform introspection and validation of requests.
+ $this->_api = Solar::dependency('Sungrazr_Controller_Server_Api',
+ $this->_config['api']);
+
+ // define special methods
+ $this->_server_specials = array(
+ 'system.listMethods' => array(&$this, 'listMethods'),
+ 'system.describe' => array(&$this, 'listMethods'),
+ 'system.methodSignature' => array(&$this, 'methodSignature'),
+ 'system.methodHelp' => array(&$this, 'methodHelp'),
+ );
+ }
+
+ /**
+ *
+ * Attach a class to a server.
+ *
+ * @param mixed $class The class to add to the server API.
+ *
+ * @param string $namespace Optionally specify a namespace for the methods
+ * of the added class. If empty, no namespace will be used.
Default is
+ * 'auto', which automatically sets the namespace as the
lowercased final
+ * segment of the class name.
+ *
+ */
+ public function addClass($class, $namespace = 'auto')
+ {
+ $this->_api->addClass($class, $namespace);
+ $this->_server_api = array_merge(
+ $this->_server_api,
+ $this->_api->api[$class]['server_api']
+ );
+ }
+
+ /**
+ *
+ * Generate a server fault.
+ *
+ * @param mixed $fault A description of the fault. Will be
serialized by
+ * the server adapter before it is returned.
+ *
+ * @param int $code Fault code
+ *
+ * @return mixed
+ *
+ */
+ abstract public function fault($fault = null, $code = 404);
+
+ /**
+ *
+ * Return the internal API information.
+ *
+ * @return Sungrazr_Controller_Server_Api
+ *
+ */
+ public function getApi()
+ {
+ return $this->_api;
+ }
+
+ /**
+ *
+ * Handle a request.
+ *
+ * Dispatches server-side call to appropriate method and returns a
+ * response.
+ *
+ * @return mixed
+ *
+ */
+ abstract public function fetch($spec = null);
+
+ /**
+ *
+ * Return a list of methods the server supports, by name.
+ *
+ * @see http://scripts.incutio.com/xmlrpc/introspection.html
+ *
+ * @see http://xmlrpc-c.sourceforge.net/introspection.html
+ *
+ * @return array
+ *
+ */
+ public function listMethods()
+ {
+ $methods = array_keys($this->_server_api);
+ foreach ($this->_server_specials as $method => $callable) {
+ $methods[] = $method;
+ }
+ sort($methods);
+ return $methods;
+ }
+
+ /**
+ *
+ * Return a description of the argument format a particular method
+ * expects.
+ *
+ * The result is an array of strings. The first element tells the type
+ * of the method's result. The rest (if any) tell the types of the
+ * method's parameters, in order.
+ *
+ * @see http://scripts.incutio.com/xmlrpc/introspection.html
+ *
+ * @see http://xmlrpc-c.sourceforge.net/introspection.html
+ *
+ * @return array
+ *
+ */
+ public function methodSignature($method = null)
+ {
+
+ }
+
+ /**
+ *
+ * Returns a text description of a particular method.
+ *
+ * The returned string is intended for human use. The server may
give as
+ * much or as little detail as it wants, including an empty string.
+ *
+ * The string may contain HTML.
+ *
+ * @see http://scripts.incutio.com/xmlrpc/introspection.html
+ *
+ * @see http://xmlrpc-c.sourceforge.net/introspection.html
+ *
+ * @return string
+ *
+ */
+ public function methodHelp($method = null)
+ {
+
+ }
+
+ /**
+ *
+ * Serializable: Returns a string representation of the instance.
+ *
+ * @return string
+ *
+ */
+ public function serialize()
+ {
+ return $this->_serialize();
+ }
+
+ /**
+ *
+ * Serializable: Populates the instance with serialized data.
+ *
+ * @return bool
+ *
+ */
+ public function unserialize($serialized)
+ {
+ return $this->_unserialize($serialized);
+ }
+
+ /**
+ *
+ * Common response method leveraging Serializable interface.
+ *
+ * @return string
+ *
+ */
+ protected function _response()
+ {
+ $response = $this->serialize();
+
+ if (! $this->_config['return']) {
+
+ // merge headers
+ $this->_headers = array_merge(
+ $this->_config['headers'],
+ $this->_headers
+ );
+
+ // set output type appropriately
+ if (empty($this->_headers['Content-Type'])) {
+ // start with default
+ $content_type = $this->_config['content_types']['response'][0];
+
+ // now check if caller would prefer something specific that
+ // is supported
+ $accept = $this->_request->http('Accept', false);
+ if ($accept && strpos($accept, '*/*') === false) {
+ // Accept header contains no wildcard
+ foreach
($this->_config['content_types']['response'] as $rt) {
+ if (strpos($accept, $rt) !== false) {
+ $content_type = $rt;
+ break;
+ }
+ }
+ }
+
+ // set the type
+ $this->_headers['Content-Type'] = $content_type;
+ }
+
+ // send content length?
+ if ($this->_config['send_length']) {
+ $len = strlen($response);
+ // any buffered output yet?
+ $buf = ob_get_contents();
+ if ($buf !== false) {
+ $buflen = strlen($buf);
+ if ($buflen != $len) {
+ $len = $len + $buflen;
+ }
+ }
+ $this->_headers['Content-Length'] = $len;
+ }
+
+ // append to Server header
+ $server_class = get_class($this);
+ if (! empty($this->_headers['Server'])) {
+ $this->_headers['Server'] = rtrim($this->_headers['Server']) .
+ ' (' . $server_class . ')';
+ } else {
+ $this->_headers['Server'] = $server_class;
+ }
+
+ if (! empty($this->_headers) && ! headers_sent()) {
+ foreach ($this->_headers as $header => $value) {
+ $h = trim($header);
+ $v = trim($value);
+ if (! empty($v)) {
+ $h .= ': ' . $v;
+ }
+ header($h);
+ }
+ }
+ echo $response;
+ return;
+ }
+ return $response;
+ }
+
+ /**
+ *
+ * serialize() Implementation details for the Serializable interface.
+ *
+ * @return string
+ *
+ */
+ abstract protected function _serialize();
+
+ /**
+ *
+ * unserialize() Implementation details for the Serializable interface.
+ *
+ * @param mixed $serialized Value to unserialize.
+ *
+ * @return bool
+ *
+ */
+ abstract protected function _unserialize($serialized);
+}
\ No newline at end of file

Added: trunk/Sungrazr/Controller/Server/Adapter/JsonRpc.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Controller/Server/Adapter/JsonRpc.php Mon Mar 10
01:36:17 2008
@@ -0,0 +1,447 @@
+<?php
+/**
+ *
+ * JSON-RPC 1.0 server adapter.
+ *
+ * Developed for, and then donated by, Mashery.com <http://mashery.com>.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server_Adapter
+ *
+ * @author Clay Loveless <cl...@killersoft.com>
+ *
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ *
+ * @version SVN: $Id$
+ *
+ */
+
+class Sungrazr_Controller_Server_Adapter_JsonRpc extends
+ Sungrazr_Controller_Server_Adapter {
+
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Config keys are ...
+ *
+ * `content_types`
+ * : (array) List of acceptable content types for inbound requests.
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Controller_Server_Adapter_JsonRpc = array(
+ 'return' => false,
+ 'headers' => array(),
+ 'content_types' => array(
+ 'request' => array(
+ 'text/javascript',
+ 'application/json'
+ ),
+ 'response' => array(
+ 'text/javascript',
+ 'text/x-javascript',
+ 'application/javascript',
+ 'application/x-javascript',
+ ),
+ ),
+ 'server_request' => array(
+ 'class' => 'Solar_Request',
+ 'spec' => null,
+ ),
+ );
+
+ /**
+ *
+ * From the request: method being called
+ *
+ * @param string
+ *
+ */
+ public $method = null;
+
+ /**
+ *
+ * From the request: unnamed parameters to pass to the method.
+ *
+ * @param array
+ *
+ */
+ public $params = null;
+
+ /**
+ *
+ * For the response: mixed result of the method call.
+ *
+ * @param mixed
+ *
+ */
+ public $result = null;
+
+ /**
+ *
+ * For the response: mixed error message. If no error occurred, this
+ * should be null. Otherwise, it should be an object.
+ *
+ * @param mixed
+ *
+ */
+ public $error = null;
+
+ /**
+ *
+ * For request and response: if request was tagged with an id, it is
+ * stored here to be attached to the response.
+ *
+ * If request was not tagged with an id, JSON-RPC 1.0 indicates that
+ * id value should be null.
+ *
+ * @param mixed
+ *
+ */
+ public $id = null;
+
+ /**
+ *
+ * Handle a JSON-RPC request.
+ *
+ * Rudimentary request validation is performed. Handler methods should
+ * perform in-depth validation on passed parameters.
+ *
+ * @param Solar_Request $request A Solar Request object. Raw POST data
+ * will be read from php://input.
+ *
+ * @return mixed
+ *
+ */
+ public function fetch($spec = null)
+ {
+ // avoid bad/inappropriate requests if possible
+ if (! $this->_validate()) {
+ return;
+ }
+
+ // call the handler object + method
+ $callback = $this->_server_api[$this->method]['callback'];
+ $obj = $this->_config['caller'];
+
+ // should we return a fault immediately?
+ // Construction/setup could have failed.
+ if ($obj->isFault === true) {
+ $fault = $obj->getFault();
+ return $this->fault(
+ $fault['message'],
+ $fault['code']
+ );
+ }
+
+ // No fault during setup, so call the method
+ $this->result = call_user_func_array(
+ array($obj, $callback[1]),
+ $this->params
+ );
+
+ // should we return a fault now?
+ if ($obj->isFault === true) {
+ $fault = $obj->getFault();
+ return $this->fault(
+ $fault['message'],
+ $fault['code']
+ );
+ }
+
+ // done!
+ return $this->_response();
+ }
+
+ /**
+ *
+ * Generate a server fault.
+ *
+ * @param mixed $fault
+ *
+ * @param int $code
+ *
+ * @return mixed
+ *
+ */
+ public function fault($fault = null, $code = 404)
+ {
+ // result MUST be null if a fault occurs.
+ $this->result = null;
+
+ // Construct an error object
+ $err = new stdClass;
+ $err->name = 'JSONRPCError';
+ $err->code = (int) $code;
+ $err->message = $fault;
+ $this->error = $err;
+ return $this->_response();
+ exit((int) $code);
+ }
+
+ /**
+ *
+ * Handle special method calls.
+ *
+ * Returns the response from the server special method if appropriate.
+ * Otherwise, returns **TRUE**.
+ *
+ * @return mixed
+ *
+ */
+ protected function _handleSpecials()
+ {
+ if (array_key_exists($this->method, $this->_server_specials)) {
+ $this->result = call_user_func_array(
+ $this->_server_specials[$this->method],
+ $this->params
+ );
+ return $this->_response();
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Read raw post data and unserialize it.
+ *
+ * @param Solar_Request $request Solar_Request object
+ *
+ * @return bool
+ *
+ */
+ protected function _loadInput()
+ {
+ $input = file_get_contents('php://input');
+ if (empty($input)) {
+ return $this->fault(
+ 'Unable to read from input',
+ 101
+ );
+ }
+
+ // pick out content length from raw input
+ $len = $this->_request->http('Content-Length', false);
+ if (! $len) {
+ return $this->fault(
+ 'No Content-Length information found in request.',
+ 102
+ );
+ }
+
+ // use Content-Length header to determine how much to retrieve
+ $body = substr($input, 0, $len);
+
+ // attempt to unserialize
+ return $this->unserialize($body);
+ }
+
+ /**
+ *
+ * Reject calls to unknown methods.
+ *
+ * Returns a server fault for unknown methods, **TRUE** if method is
+ * known.
+ *
+ * @return mixed
+ *
+ */
+ protected function _rejectUnknownMethods()
+ {
+ if (empty($this->_server_api[$this->method])) {
+ return $this->fault(
+ 'Unknown method: ' . $this->method,
+ 103
+ );
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Serialize the data in this object to a JSON-RPC response.
+ *
+ * @return string
+ *
+ */
+ protected function _serialize()
+ {
+ return json_encode(array(
+ 'result' => $this->result,
+ 'error' => $this->error,
+ 'id' => $this->id
+ ));
+ }
+
+ /**
+ *
+ * Populate this request with data specified in the serialized request.
+ *
+ * @return bool
+ *
+ */
+ protected function _unserialize($serialized)
+ {
+ // JSON-RPC 1.0 properties
+ $properties = array('method', 'params', 'id');
+ $obj = json_decode($serialized);
+
+ if (! is_object($obj)) {
+ return $this->fault(
+ 'The request was not a valid JSON object; It could not
be parsed.',
+ -1
+ );
+ }
+
+ foreach ($properties as $p) {
+ if (! empty($obj->{$p})) {
+ $this->{$p} = $obj->{$p};
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Wrapper for all validation methods.
+ *
+ * @return bool
+ *
+ */
+ protected function _validate()
+ {
+ // JSON-RPC over HTTP must be a POST
+ $ok = $this->_validateRequestMethod();
+ if ($ok !== true) return false;
+
+ // Confirm that the content-type is acceptable
+ $ok = $this->_validateRequestContentType();
+ if ($ok !== true) return false;
+
+ // attempt to load the request
+ $ok = $this->_loadInput();
+ if ($ok !== true) return false;
+
+ // special request?
+ $ok = $this->_handleSpecials();
+ if ($ok !== true) return false;
+
+ // is this an unknown method?
+ $ok = $this->_rejectUnknownMethods();
+ if ($ok !== true) return false;
+
+ // validate type
+ $ok = $this->_validateParameterType();
+ if ($ok !== true) return false;
+
+ // validate parameter count
+ $ok = $this->_validateParameterCount();
+ if ($ok !== true) return false;
+
+ return true;
+ }
+
+ /**
+ *
+ * Validate the parameter count against what the handler method is
+ * expecting.
+ *
+ * Returns a server fault on failure, **TRUE** on success.
+ *
+ * @return mixed
+ *
+ */
+ protected function _validateParameterCount()
+ {
+ $expected_num = count($this->_server_api[$this->method]['params']);
+ $actual_num = count($this->params);
+
+ if ($actual_num != $expected_num) {
+ return $this->fault(
+ "Invalid call to {$this->method}. " .
+ "Requires {$expected_num} parameters, " .
+ "{$actual_num} parameters received",
+ 400
+ );
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Make sure the params value is an array.
+ *
+ * Returns a server fault on failure, **TRUE** on success.
+ *
+ * @return mixed
+ *
+ */
+ protected function _validateParameterType()
+ {
+ // params should be an array
+ if (! is_array($this->params)) {
+ return $this->fault(
+ "JSON-RPC expects \"params\" to be an array of objects.",
+ 400
+ );
+ }
+
+ // each element of params should be an object
+ foreach ($this->params as $param) {
+ if (! is_object($param)) {
+ return $this->fault(
+ "JSON-RPC expects \"params\" to be an array of objects.",
+ 400
+ );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Make sure the request content type is acceptable.
+ *
+ * Returns a server fault on failure, **TRUE** on success.
+ *
+ * @return mixed
+ *
+ */
+ protected function _validateRequestContentType()
+ {
+ return true;
+ $ct = strtolower($this->_request->server('CONTENT_TYPE', 'unknown'));
+ if (! in_array($ct,
$this->_config['content_types']['request'])) {
+ return $this->fault(
+ "JSON-RPC does not support Content-Type {$ct} requests.",
+ 400
+ );
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Make sure the request is a POST.
+ *
+ * Returns a server fault on failure, **TRUE** on success.
+ *
+ * @return mixed
+ *
+ */
+ protected function _validateRequestMethod()
+ {
+ // JSON-RPC over HTTP must be a POST
+ if (! $this->_request->isPost()) {
+ return $this->fault(
+ 'JSON-RPC calls over HTTP must be made using POST requests',
+ 100
+ );
+ }
+ return true;
+ }
+}
\ No newline at end of file

Added: trunk/Sungrazr/Controller/Server/Api.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Controller/Server/Api.php Mon Mar 10 01:36:17 2008
@@ -0,0 +1,246 @@
+<?php
+/**
+ *
+ * Reflection-based server introspection.
+ *
+ * Developed for, and then donated by, Mashery.com <http://mashery.com>.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server
+ *
+ * @subpackage Sungrazr_Controller_Server_Api
+ *
+ * @author Clay Loveless <cl...@killersoft.com>
+ *
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ *
+ * @version SVN: $Id$
+ *
+ */
+class Sungrazr_Controller_Server_Api extends Solar_Docs_Apiref {
+
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Config keys are ...
+ *
+ * `phpdoc`
+ * : (dependency) Config container for the Sungrazr_Docs_Phpdoc
+ * dependency object.
+ *
+ * `cache`
+ * : (array) Factory configuration for a Solar_Cache adapter.
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Controller_Server_Api = array(
+ 'phpdoc' => null,
+ 'cache' => array(
+ 'adapter' => 'Solar_Cache_Adapter_File',
+ 'active' => true,
+ 'life' => 86400,
+ ),
+ );
+
+ /**
+ *
+ * PHP's Magic Methods, these are ignored.
+ *
+ * @var array
+ *
+ */
+ protected $_magic_methods = array(
+ '__construct',
+ '__destruct',
+ '__get',
+ '__set',
+ '__call',
+ '__sleep',
+ '__wakeup',
+ '__isset',
+ '__unset',
+ '__tostring',
+ '__clone',
+ '__set_state',
+ );
+
+ /**
+ *
+ * Cache keys generated by the _fetchCachedClass() method, which
+ * are susequently used to add a parsed class to the cache.
+ *
+ * @param array
+ *
+ */
+ protected $_class_cache_keys = array();
+
+ /**
+ *
+ * Solar_Cache object for caching parsed class information.
+ *
+ * @param Solar_Cache_Adapter
+ *
+ */
+ protected $_cache;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param array $config User-provided configuration values.
+ *
+ */
+ public function __construct($config = null)
+ {
+ // set the phpdoc parser to Sungrazr extension
+ $this->_Sungrazr_Controller_Server_Api['phpdoc'] = Solar::factory(
+ 'Sungrazr_Docs_Phpdoc'
+ );
+
+ // basic construction
+ parent::__construct($config);
+
+ // set up class cache
+ if (! empty($this->_config['cache'])) {
+ $this->_cache = Solar::factory('Solar_Cache',
+ $this->_config['cache']
+ );
+ }
+ }
+
+ /**
+ *
+ * Performs class/object reflection for server introspection.
+ *
+ * Introspection is performed by leveraging the reflection/introspection
+ * capabilities found in Solar_Docs_Apiref. Since such
introspection is
+ * computationally expensive, aggressive caching (24 hours by
default) is
+ * done using the output of hash_file() on the class file as keys.
+ *
+ * @param mixed $class The class to add to the server API. If an
object is
+ * passed, the class will be picked out using get_class().
+ *
+ * @param string $namespace Optionally specify a namespace for the methods
+ * of the added class. If empty, no namespace will be used.
Default is
+ * 'auto', which automatically sets the namespace as the
lowercased final
+ * segment of the class name.
+ *
+ * @return bool
+ *
+ */
+ public function addClass($class, $namespace = 'auto')
+ {
+ if (is_object($class)) {
+ $class = get_class($class);
+ }
+
+ // fetch from cache if possible
+ if ($cached = $this->_fetchCachedClass($class)) {
+ $this->api[$class] = $cached;
+ return true;
+ }
+
+ $added = parent::addClass($class);
+
+ if (! $added) {
+ return false;
+ }
+
+ // drop Solar_Base descendants and magic methods
+ foreach ($this->api[$class]['methods'] as $method => $info) {
+ if ($info['from'] == 'Solar_Base' ||
+ in_array($method, $this->_magic_methods)) {
+ unset($this->api[$class]['methods'][$method]);
+ }
+ }
+
+ // drop non-public properties, and properties from Solar_Base
+ foreach ($this->api[$class]['properties'] as $property =>
$info) {
+ if ($info['from'] == 'Solar_Base' ||
$info['access'] != 'public') {
+ unset($this->api[$class]['properties'][$property]);
+ }
+ }
+
+ // remap with namespace
+ $ns = '';
+ if (! empty($namespace)) {
+ if ($namespace == 'auto') {
+ $parts = explode('_', $class);
+ $last = end($parts);
+ $ns = strtolower($last) . '.';
+ } else {
+ $ns = (string) $namespace . '.';
+ }
+ }
+
+ // populate server api
+ $this->api[$class]['server_api'] = array();
+ foreach ($this->api[$class]['methods'] as $method => $info) {
+ if ($info['access'] == 'public') {
+ $server_method = $ns . $method;
+ // keep track of where method originated
+ $info['callback'] = array($class, $method);
+ $this->api[$class]['server_api'][$server_method] = $info;
+ }
+ }
+
+ // cache result
+ $this->_addCachedClass($class);
+
+ // done!
+ return true;
+ }
+
+ /**
+ *
+ * Fetch cached API reference output.
+ *
+ * Attempt to retrieve the Apiref-parsed array for the class. Since
+ * parsing is expensive, avoid retrieving any more often than necessary
+ * by using hash_file() to generate a cache key for the named class.
+ *
+ * This causes us to hit the disk, but is less expensive than the full
+ * Apiref-parsing operation w/reflection.
+ *
+ * @param string $class Class name to retrieve from cache.
+ *
+ */
+ protected function _fetchCachedClass($class)
+ {
+ // nothing to do if a cache adapter is not configured
+ if (empty($this->_cache)) {
+ return false;
+ }
+
+ $path = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
+ if ($full_path = Solar_File::exists($path)) {
+ // get hash of file in this location
+ $this->_class_cache_keys[$class] = hash_file('md5', $full_path);
+ }
+
+ return $this->_cache->fetch($this->_class_cache_keys[$class]);
+ }
+
+ /**
+ *
+ * Add a class API reference to the cache.
+ *
+ * @param string $class Name of class API to cache.
+ *
+ */
+ protected function _addCachedClass($class)
+ {
+ // nothing to do if a cache adapter is not configured
+ if (empty($this->_cache)) {
+ return false;
+ }
+
+ return $this->_cache->add(
+ $this->_class_cache_keys[$class],
+ $this->api[$class]
+ );
+ }
+}
\ No newline at end of file

Added: trunk/Sungrazr/Controller/Service.php
==============================================================================
--- (empty file)
+++ trunk/Sungrazr/Controller/Service.php Mon Mar 10 01:36:17 2008
@@ -0,0 +1,232 @@
+<?php
+/**
+ *
+ * The equivalent of a page-controller to handle service requests.
+ *
+ * Provides a base for building external API wrapper classes.
+ *
+ * Developed for, and then donated by, Mashery.com <http://mashery.com>.
+ *
+ * @category Tipos
+ *
+ * @package Sungrazr_Controller_Server
+ *
+ * @author Clay Loveless <cl...@killersoft.com>
+ *
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ *
+ * @version SVN: $Id$
+ *
+ */
+
+abstract class Sungrazr_Controller_Service extends Solar_Base {
+
+ /**
+ *
+ * User-provided configuration.
+ *
+ * Config keys are ...
+ *
+ * `server_request`
+ * : (dependency) Server's Solar_Request dependency
+ *
+ * `server`
+ * : (dependency) Server config
+ *
+ * @var array
+ *
+ */
+ protected $_Sungrazr_Controller_Service = array(
+ 'server_request' => array(
+ 'class' => 'Solar_Request',
+ 'spec' => null,
+ ),
+ 'server' => null,
+ );
+
+ /**
+ *
+ * Flag to enable checking for fault responses.
+ *
+ * @var bool
+ *
+ */
+ public $isFault;
+
+ /**
+ *
+ * Fault details.
+ *
+ * @var array
+ *
+ */
+ protected $_fault = array(
+ 'message' => null,
+ 'code' => 0
+ );
+
+ /**
+ *
+ * The Server controller.
+ *
+ * @var Sungrazr_Controller_Server
+ *
+ */
+ protected $_server;
+
+ /**
+ *
+ * The server's Solar_Request dependency
+ *
+ * @var Solar_Request
+ *
+ */
+ protected $_request;
+
+ /**
+ *
+ * Web Services request type, picked from last segment of the caller's
+ * class name and lowercased. (Ex: jsonrpc, xmlrpc, rest, etc.)
+ *
+ * @var string
+ *
+ */
+ protected $_type;
+
+ /**
+ *
+ * The front-controller object (if any) that invoked this page-controller.
+ *
+ * @var Solar_Controller_Front
+ *
+ */
+ protected $_front;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param array $config User-provided configuration values.
+ *
+ */
+ public function __construct($config = null)
+ {
+ parent::__construct($config);
+
+ // set up request dependency
+ if (! Solar_Registry::exists('server_request')) {
+ Solar_Registry::set(
+ 'server_request',
+ $this->_config['server_request']['class'],
+ $this->_config['server_request']['spec']
+ );
+ }
+ $this->_request = Solar_Registry::get('server_request');
+
+ // Setup the server.
+ $config = (array) $this->_config['server'];
+ $config['caller'] = $this;
+ $this->_server = Solar::factory('Sungrazr_Controller_Server', $config);
+
+ // Set the server type.
+ $parts = explode('_', get_class($this->_server));
+ $last = end($parts);
+ $this->_type = strtolower($last);
+
+ // Add this class to the api.
+ $this->_server->addClass(get_class($this));
+
+ // special setup
+ $this->_setup();
+ }
+
+ /**
+ *
+ * Post-construction setup.
+ *
+ * @return void
+ *
+ */
+ protected function _setup()
+ {
+ }
+
+ /**
+ *
+ * Executes the requested action and returns its output with layout.
+ *
+ * If an exception is thrown during the fetch() process, it is caught
+ * and sent along to the _rescueException() method, which may generate
+ * and return alternative output.
+ *
+ * @param string $spec The action specification string, for example,
+ * "tags/php+framework" or "user/pmjones/php+framework?page=3"
+ *
+ * @return Solar_Http_Response A response object with headers and
body from
+ * the action, view, and layout.
+ *
+ */
+ public function fetch($spec = null)
+ {
+ return $this->_server->fetch();
+ }
+
+ /**
+ *
+ * Return the fault
+ *
+ * @return array
+ *
+ */
+ public function getFault()
+ {
+ return $this->_fault;
+ }
+
+ /**
+ *
+ * Raise the fault flag.
+ *
+ * @param mixed $fault A description of the fault. Will be
serialized by
+ * the server adapter before it is returned.
+ *
+ * @param int $code Fault code
+ *
+ * @return void
+ *
+ */
+ protected function _raiseFault($fault = null, $code = 404)
+ {
+ $this->isFault = true;
+ $this->_fault['message'] = $fault;
+ $this->_fault['code'] = (int) $code;
+ }
+
+ /**
+ *
+ * Clear the fault.
+ *
+ * @return void
+ *
+ */
+ protected function _clearFault()
+ {
+ $this->isFault = false;
+ $this->_fault['message'] = null;
+ $this->_fault['code'] = 0;
+ }
+
+ /**
+ *
+ * Injects the front-controller object that invoked this page-controller.
+ *
+ * @param Solar_Controller_Front $front The front-controller.
+ *
+ * @return void
+ *
+ */
+ public function setFrontController($front)
+ {
+ $this->_front = $front;
+ }
+}
\ No newline at end of file

Reply all
Reply to author
Forward
0 new messages