From b2a0a6cebd89672eebaa7e2c84317dd5c5f2c07c Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Fri, 17 Aug 2012 15:59:47 +0000 Subject: [PATCH] Created RequestProcessor class --- .../webim/libs/classes/request_processor.php | 444 ++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 src/messenger/webim/libs/classes/request_processor.php diff --git a/src/messenger/webim/libs/classes/request_processor.php b/src/messenger/webim/libs/classes/request_processor.php new file mode 100644 index 00000000..bf2b9d54 --- /dev/null +++ b/src/messenger/webim/libs/classes/request_processor.php @@ -0,0 +1,444 @@ +RequestReceived + * - ReceiveRequestError + * - ResponseReceived + * - CallError + * - FunctionCall + * + * variable specifies in RequestProcessor::__construct() + * + * @see RequestProcessor::__construct() + * @see RequestProcessor::registerEvents() + */ +abstract class RequestProcessor { + + /** + * Instance of the MibewAPI class + * @var type + */ + protected $mibewAPI = null; + + /** + * Prefix that uses for all registered by the class events. + * @var string + */ + protected $eventPrefix = ''; + + /** + * Array of the responses packages + * @var array + */ + protected $responses = array(); + + /** + * Array of configurations + * @var array + */ + protected $config = array(); + + /** + * Class constructor + * + * @param type $config Configuration data. + * It must contains following keys: + * - 'signature': Use for verification sender + * - 'trusted_signatures': array of trusted signatures. Uses for identify another + * side of interaction. + * And may contains following (if not default values will be used) + * - 'event_prefixport': prefix that uses for all registered by the class events. The default value is the class + * name with first character in lower case + * @todo think about errors' level + */ + public function __construct($config) { + // Check signature + if (! isset($config['signature'])) { + trigger_error("Signature is not specified", E_USER_ERROR); + } + + // Check trusted signatures + if (! isset($config['trusted_signatures'])) { + trigger_error("Trusted signatures is not specified", E_USER_ERROR); + } + + // Get an instance of the MibewAPI class + $this->mibewAPI = $this->getMibewAPIInstance(); + + // Get class name and prefix for events and etc. + $class_name = get_class($this); + $this->eventPrefix = empty($config['event_prefix']) + ? strtolower(substr($class_name, 0, 1)) . substr($class_name, 1) + : $config['event_prefix']; + + // Store config + $this->config = $config; + + // Register Events + $this->registerEvents(); + } + + /** + * Proccess received packages + * + * On any error function returns only boolean false. To handle error add listener to the + * "ReceiveRequestError" event. + * + * @param string $package Encoded package + * @return boolean true if request processed succussfully or false on failure + */ + public function receiveRequest($package){ + $dispatcher = EventDispatcher::getInstance(); + // Try to handle request + try { + // Decode package + $request_package = $this->mibewAPI->decodePackage( + $package, + $this->config['trusted_signatures'] + ); + + // Trigger request received event + $vars = array('package' => $request_package); + $dispatcher->triggerEvent( + $this->eventPrefix . 'RequestReceived', + $vars + ); + $package = $vars['package']; + + // Process requests in package + // Clear responses + $this->responses = array(); + foreach ($package['requests'] as $request) { + // Try to load callback function for this token + $callback = $this->loadCallback($request['token']); + $need_result = ! is_null($callback); + $arguments = $this->processRequest($request, $need_result); + + if ($need_result) { + // There is callback function + // TODO: Think about callback functions nature + $object = $callback['object']; + $method = $callback['method']; + $object->$method($arguments); + } else { + // There is no callback function + $this->responses[] = $this->mibewAPI->buildResult( + $request['token'], + $arguments + ); + } + } + + if ($request_package['async']) { + $this->sendAsyncResponses($this->responses); + } else { + $this->sendSyncResponses($this->responses); + } + + // Output response + } catch (Exception $e) { + // Something went wrong. Trigger error event + $vars = array('exception' => $e); + $dispatcher->triggerEvent($this->eventPrefix . 'RequestError', $vars); + return false; + } + return true; + } + + /** + * Call functions at the other side + * + * On any error function returns only boolean false. To handle error add listener to the + * "CallError" event. + * + * @param array $functions Array of functions. See Mibew API for details. + * @param boolean $async True for asynchronous requests and false for synchronous request + * @param mixed $callback callback array or null for synchronous requests. + * @return mixed request result or boolean false on failure. + */ + public function call($functions, $async, $callback = null) { + // Get an instance of the EventDispatcher class + $dispatcher = EventDispatcher::getInstance(); + // Try to call function at Other side + try { + // Check functions to call + if (! is_array($functions)) { + throw new RequestProcessorException( + '#1 argument must be an array!', + RequestProcessorException::WRONG_ARGUMENTS + ); + } + foreach ($functions as $function) { + $this->mibewAPI->checkFunction($function, true); + } + + // Create request + $token = md5(microtime() . rand()); + $request = array( + 'token' => $token, + 'functions' => $functions + ); + + if ($async) { + // TODO: Think about callbacks + // TODO: May be add exception if $callback = null + + // Store callback + $this->storeCallback($callback); + + // Send asynchronous request + $this->sendAsyncRequest($request); + return true; + } + + // Send synchronous request + $response_package = $this->sendSyncRequest($request); + + // Trigger response received event + $vars = array('package' => $response_package); + $dispatcher->triggerEvent($this->eventPrefix . 'ResponseReceived', $vars); + + // Process requests in response + $result = null; + foreach ($response_package['requests'] as $request) { + // Use only response with token equals to request token. Ignore other packages. + // TODO: May be not ignore other packages + if ($request['token'] == $token) { + $result = $this->processRequest($request, true); + } + } + + if (is_null($result)) { + throw new RequestProcessorException( + "There is no 'result' function in response", + RequestProcessorException::NO_RESULT_FUNCTION + ); + } + } catch (Exception $e) { + // Trigger error event + $vars = array('exception' => $e); + $dispatcher->triggerEvent($this->eventPrefix . "CallError", $vars); + return false; + } + return $result; + } + + /** + * Register events + * + * Registered Events: + * + * 1. "RequestReceived" - triggers when request decoded and validate + * successfully, before execution functions from request. + * + * An associative array passed to event handler have following keys: + * - 'package' : decoded and validated package array. See Mibew API for details of the + * package structure + * + * + * 2. "ReceiveRequestError" - triggers when error occurs during received + * request processing. + * + * An associative array passed to event handler have following keys: + * - 'exception' : an object of Exception (or inherited) class related to occurred error. + * + * + * 3. "ResponseReceived" - triggers when request sent successfully, and + * response received. + * + * An associative array passed to event handler have following keys: + * - 'package' : decoded and validated response package array. See Mibew API for details of + * the package structure. + * + * + * 4. "CallError" - triggers when error occurs in + * call() method. + * + * An associative array passed to event handler have following keys: + * - 'exception' : an object of Exception (or inherited) class related to occurred error. + * + * + * 5. "FunctionCall" - triggers when function from request calls. + * + * An associative array passed to event handler is 'function' array. See Mibew API for + * detail of the 'function' array structure. + * + * If function wants to return some results, it should add results to the 'results' element + * of the function array. + * + * Example of the event handler: + * + * public function callHandler(&$function) { + * if ($function['function'] == 'microtime') { + * $as_float = empty($function['arguments']['as_float']) + * ? false + * : $function['arguments']['as_float']; + * $function['results']['time'] = microtime($as_float); + * } + * } + * + */ + protected function registerEvents() { + $dispatcher = EventDispatcher::getInstance(); + $dispatcher->registerEvent($this->eventPrefix . 'RequestReceived'); + $dispatcher->registerEvent($this->eventPrefix . 'RequestError'); + $dispatcher->registerEvent($this->eventPrefix . 'ResponseReceived'); + $dispatcher->registerEvent($this->eventPrefix . 'CallError'); + $dispatcher->registerEvent($this->eventPrefix . 'FunctionCall'); + } + + /** + * Process request + * + * @param array $request 'Requests' array. See Mibew API for details. + * @param mixed $result_function Control existance of the 'result' function in request. + * Use boolean true if 'result' function must exists in request, boolean false if must not + * and null if it doesn't matter. + * @return array Array of requests results. + */ + protected function processRequest($request, $result_function = null) { + $context = new MibewAPIExecutionContext(); + + // Get result functions + $result_function = $this->mibewAPI->getResultFunction( + $request['functions'], + $result_function + ); + + // Request contains not only result function + if (! is_null($result_function) && count($request['functions']) > 1) { + trigger_error( + 'Request contains not only result function', + E_USER_WARNING + ); + } + + if (is_null($result_function)) { + // Execute functions + foreach ($request['functions'] as $function) { + $this->processFunction($function, $context); + } + return $context->getResults(); + } else { + // Return result + return $result_function['arguments']; + } + } + + /** + * Process function + * + * @param array $function 'Function' array. See Mibew API for details + * @param MibewAPIExecutionContext Execution context + * @return array Result of function call + */ + protected function processFunction($function, MibewAPIExecutionContext &$context) { + // Get function arguments with replaced references + $arguments = $context->getArgumentsList($function); + + $call_vars = array( + 'function' => $function['function'], + 'arguments' => $arguments, + 'results' => array() + ); + + // Call processor function + $this->processorCall($call_vars); + + // Trigger FunctionCall event + $dispatcher = EventDispatcher::getInstance(); + $dispatcher->triggerEvent($this->eventPrefix . 'FunctionCall', $call_vars); + + // Get results + $results = $call_vars['results']; + + // Add function results to execution context + $context->storeFunctionResults($function, $results); + } + + /** + * Creates and returns an instance of the MibewAPI class. + * + * @return MibewAPI + */ + protected abstract function getMibewAPIInstance(); + + /** + * Loads callback function + * + * @param string $token Token of the request related to callback function + * @return mixed callback function array or null if callback function not exists + */ + protected abstract function loadCallback($token); + + /** + * Dispatcher of the functions, provided by the RequestProcessor (or inherited) classes as an external API. + * + * It calls before 'FunctionCall' event triggers. + * + * @param array &$func Function array equals to array, passed to the 'FunctionCall' event. + * @see RequestProcessor::registerEvents() + */ + protected abstract function processorCall(&$func); + + /** + * Sends synchronous request + * + * @param array $request The 'request' array. See Mibew API for details + * @return mixed response array or boolean false on failure + */ + protected abstract function sendSyncRequest($request); + + /** + * Sends asynchronous request + * + * @param array $request The 'request' array. See Mibew API for details + * @return boolean true on success or false on failure + */ + protected abstract function sendAsyncRequest($request); + + /** + * Sends synchronous responses + * + * @param array $responses An array of the 'Request' arrays. See Mibew API for details + */ + protected abstract function sendSyncResponses($responses); + + /** + * Sends asynchronous responses + * + * @param array $responses An array of the 'Request' arrays. See Mibew API for details + */ + protected abstract function sendAsyncResponses($responses); +} + +class RequestProcessorException extends Exception { + /** + * Result function is absent + */ + const NO_RESULT_FUNCTION = 1; + /** + * Wrong function arguments + */ + const WRONG_ARGUMENTS = 2; +} + +?> \ No newline at end of file