diff --git a/webservice/classes/rest/Abstract.php b/webservice/classes/rest/Abstract.php new file mode 100644 index 0000000..7658a6d --- /dev/null +++ b/webservice/classes/rest/Abstract.php @@ -0,0 +1,221 @@ +_table = new Rest_Definition(); + $this->_table->setOverwriteExistingMethods($this->_overwriteExistingMethods); + } + + /** + * Returns a list of registered methods + * + * Returns an array of method definitions. + * + * @return Rest_Definition + */ + public function getFunctions() + { + return $this->_table; + } + + /** + * Lowercase a string + * + * Lowercase's a string by reference + * + * @deprecated + * @param string $string value + * @param string $key + * @return string Lower cased string + */ + public static function lowerCase(&$value, &$key) + { + trigger_error(__CLASS__ . '::' . __METHOD__ . '() is deprecated and will be removed in a future version', E_USER_NOTICE); + return $value = strtolower($value); + } + + /** + * Build callback for method signature + * + * @param Rest_Reflection_Function_Abstract $reflection + * @return Rest_Method_Callback + */ + protected function _buildCallback(Rest_Reflection_Function_Abstract $reflection) + { + $callback = new Rest_Method_Callback(); + if ($reflection instanceof Rest_Reflection_Method) { + $callback->setType($reflection->isStatic() ? 'static' : 'instance') + ->setClass($reflection->getDeclaringClass()->getName()) + ->setMethod($reflection->getName()); + } elseif ($reflection instanceof Rest_Reflection_Function) { + $callback->setType('function') + ->setFunction($reflection->getName()); + } + return $callback; + } + + /** + * Build a method signature + * + * @param Rest_Reflection_Function_Abstract $reflection + * @param null|string|object $class + * @return Rest_Method_Definition + * @throws Rest_Exception on duplicate entry + */ + protected function _buildSignature(Rest_Reflection_Function_Abstract $reflection, $class = null) + { + $ns = $reflection->getNamespace(); + $name = $reflection->getName(); + $method = empty($ns) ? $name : $ns . '.' . $name; + + if (!$this->_overwriteExistingMethods && $this->_table->hasMethod($method)) { + require_once 'Exception.php'; + throw new Rest_Exception('Duplicate method registered: ' . $method); + } + + $definition = new Rest_Method_Definition(); + $definition->setName($method) + ->setCallback($this->_buildCallback($reflection)) + ->setMethodHelp($reflection->getDescription()) + ->setInvokeArguments($reflection->getInvokeArguments()); + + foreach ($reflection->getPrototypes() as $proto) { + $prototype = new Rest_Method_Prototype(); + $prototype->setReturnType($this->_fixType($proto->getReturnType())); + foreach ($proto->getParameters() as $parameter) { + $param = new Rest_Method_Parameter(array( + 'type' => $this->_fixType($parameter->getType()), + 'name' => $parameter->getName(), + 'optional' => $parameter->isOptional(), + )); + if ($parameter->isDefaultValueAvailable()) { + $param->setDefaultValue($parameter->getDefaultValue()); + } + $prototype->addParameter($param); + } + $definition->addPrototype($prototype); + } + if (is_object($class)) { + $definition->setObject($class); + } + $this->_table->addMethod($definition); + return $definition; + } + + /** + * Dispatch method + * + * @param Rest_Method_Definition $invocable + * @param array $params + * @return mixed + */ + protected function _dispatch(Rest_Method_Definition $invocable, array $params) + { + $callback = $invocable->getCallback(); + $type = $callback->getType(); + + if ('function' == $type) { + $function = $callback->getFunction(); + return call_user_func_array($function, $params); + } + + $class = $callback->getClass(); + $method = $callback->getMethod(); + + if ('static' == $type) { + return call_user_func_array(array($class, $method), $params); + } + + $object = $invocable->getObject(); + if (!is_object($object)) { + $invokeArgs = $invocable->getInvokeArguments(); + if (!empty($invokeArgs)) { + $reflection = new ReflectionClass($class); + $object = $reflection->newInstanceArgs($invokeArgs); + } else { + $object = new $class; + } + } + return call_user_func_array(array($object, $method), $params); + } + + /** + * Map PHP type to protocol type + * + * @param string $type + * @return string + */ + abstract protected function _fixType($type); +} diff --git a/webservice/classes/rest/Definition.php b/webservice/classes/rest/Definition.php new file mode 100644 index 0000000..c271de3 --- /dev/null +++ b/webservice/classes/rest/Definition.php @@ -0,0 +1,243 @@ +setMethods($methods); + } + } + + /** + * Set flag indicating whether or not overwriting existing methods is allowed + * + * @param mixed $flag + * @return void + */ + public function setOverwriteExistingMethods($flag) + { + $this->_overwriteExistingMethods = (bool) $flag; + return $this; + } + + /** + * Add method to definition + * + * @param array|Rest_Method_Definition $method + * @param null|string $name + * @return Rest_Definition + * @throws Rest_Exception if duplicate or invalid method provided + */ + public function addMethod($method, $name = null) + { + if (is_array($method)) { + require_once 'classes/rest/method/Definition.php'; + $method = new Rest_Method_Definition($method); + } elseif (!$method instanceof Rest_Method_Definition) { + require_once 'Exception.php'; + throw new Rest_Exception('Invalid method provided'); + } + + if (is_numeric($name)) { + $name = null; + } + if (null !== $name) { + $method->setName($name); + } else { + $name = $method->getName(); + } + if (null === $name) { + require_once 'Exception.php'; + throw new Rest_Exception('No method name provided'); + } + + if (!$this->_overwriteExistingMethods && array_key_exists($name, $this->_methods)) { + require_once 'Exception.php'; + throw new Rest_Exception(sprintf('Method by name of "%s" already exists', $name)); + } + $this->_methods[$name] = $method; + return $this; + } + + /** + * Add multiple methods + * + * @param array $methods Array of Rest_Method_Definition objects or arrays + * @return Rest_Definition + */ + public function addMethods(array $methods) + { + foreach ($methods as $key => $method) { + $this->addMethod($method, $key); + } + return $this; + } + + /** + * Set all methods at once (overwrite) + * + * @param array $methods Array of Rest_Method_Definition objects or arrays + * @return Rest_Definition + */ + public function setMethods(array $methods) + { + $this->clearMethods(); + $this->addMethods($methods); + return $this; + } + + /** + * Does the definition have the given method? + * + * @param string $method + * @return bool + */ + public function hasMethod($method) + { + return array_key_exists($method, $this->_methods); + } + + /** + * Get a given method definition + * + * @param string $method + * @return null|Rest_Method_Definition + */ + public function getMethod($method) + { + if ($this->hasMethod($method)) { + return $this->_methods[$method]; + } + return false; + } + + /** + * Get all method definitions + * + * @return array Array of Rest_Method_Definition objects + */ + public function getMethods() + { + return $this->_methods; + } + + /** + * Remove a method definition + * + * @param string $method + * @return Rest_Definition + */ + public function removeMethod($method) + { + if ($this->hasMethod($method)) { + unset($this->_methods[$method]); + } + return $this; + } + + /** + * Clear all method definitions + * + * @return Rest_Definition + */ + public function clearMethods() + { + $this->_methods = array(); + return $this; + } + + /** + * Cast definition to an array + * + * @return array + */ + public function toArray() + { + $methods = array(); + foreach ($this->getMethods() as $key => $method) { + $methods[$key] = $method->toArray(); + } + return $methods; + } + + /** + * Countable: count of methods + * + * @return int + */ + public function count() + { + return count($this->_methods); + } + + /** + * Iterator: current item + * + * @return mixed + */ + public function current() + { + return current($this->_methods); + } + + /** + * Iterator: current item key + * + * @return int|string + */ + public function key() + { + return key($this->_methods); + } + + /** + * Iterator: advance to next method + * + * @return void + */ + public function next() + { + return next($this->_methods); + } + + /** + * Iterator: return to first method + * + * @return void + */ + public function rewind() + { + return reset($this->_methods); + } + + /** + * Iterator: is the current index valid? + * + * @return bool + */ + public function valid() + { + return (bool) $this->current(); + } +} diff --git a/webservice/classes/rest/Exception.php b/webservice/classes/rest/Exception.php new file mode 100644 index 0000000..d497f47 --- /dev/null +++ b/webservice/classes/rest/Exception.php @@ -0,0 +1,14 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ + +/** + * Rest_Interface + */ +require_once 'classes/rest/Interface.php'; + +/** + * Rest_Reflection + */ +require_once 'classes/rest/Reflection.php'; + +/** + * Rest_Abstract + */ +require_once 'classes/rest/Abstract.php'; + +/** + * Rest_Exception + * + */ +require_once 'classes/rest/Exception.php'; + +class Rest_Server implements Rest_Interface +{ + /** + * Class Constructor Args + * @var array + */ + protected $_args = array(); + + /** + * @var string Encoding + */ + protected $_encoding = 'UTF-8'; + + /** + * @var array An array of Rest_Reflect_Method + */ + protected $_functions = array(); + + /** + * @var array Array of headers to send + */ + protected $_headers = array(); + + /** + * @var array PHP's Magic Methods, these are ignored + */ + protected static $magicMethods = array( + '__construct', + '__destruct', + '__get', + '__set', + '__call', + '__sleep', + '__wakeup', + '__isset', + '__unset', + '__tostring', + '__clone', + '__set_state', + ); + + /** + * @var string Current Method + */ + protected $_method; + + /** + * @var Rest_Reflection + */ + protected $_reflection = null; + + /** + * Whether or not {@link handle()} should send output or return the response. + * @var boolean Defaults to false + */ + protected $_returnResponse = false; + + /** + * Constructor + */ + public function __construct() + { + set_exception_handler(array($this, "fault")); + $this->_reflection = new Rest_Reflection(); + } + + /** + * Set XML encoding + * + * @param string $encoding + * @return Rest_Server + */ + public function setEncoding($encoding) + { + $this->_encoding = (string) $encoding; + return $this; + } + + /** + * Get XML encoding + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Lowercase a string + * + * Lowercase's a string by reference + * + * @param string $value + * @param string $key + * @return string Lower cased string + */ + public static function lowerCase(&$value, &$key) + { + return $value = strtolower($value); + } + + /** + * Whether or not to return a response + * + * If called without arguments, returns the value of the flag. If called + * with an argument, sets the flag. + * + * When 'return response' is true, {@link handle()} will not send output, + * but will instead return the response from the dispatched function/method. + * + * @param boolean $flag + * @return boolean|Rest_Server Returns Rest_Server when used to set the flag; returns boolean flag value otherwise. + */ + public function returnResponse($flag = null) + { + if (null == $flag) { + return $this->_returnResponse; + } + + $this->_returnResponse = ($flag) ? true : false; + return $this; + } + + /** + * Implement Rest_Interface::handle() + * + * @param array $request + * @throws Rest_Server_Exception + * @return string|void + */ + public function handle($request = false) + { + $this->_headers = array('Content-Type: text/xml'); + if (!$request) { + $request = $_REQUEST; + } + if (isset($request['method'])) { + $this->_method = $request['method']; + if (isset($this->_functions[$this->_method])) { + if ($this->_functions[$this->_method] instanceof Rest_Reflection_Function || $this->_functions[$this->_method] instanceof Rest_Reflection_Method && $this->_functions[$this->_method]->isPublic()) { + $request_keys = array_keys($request); + array_walk($request_keys, array(__CLASS__, "lowerCase")); + $request = array_combine($request_keys, $request); + + $func_args = $this->_functions[$this->_method]->getParameters(); + + $calling_args = array(); + foreach ($func_args as $arg) { + if (isset($request[strtolower($arg->getName())])) { + $calling_args[] = $request[strtolower($arg->getName())]; + } elseif ($arg->isOptional()) { + $calling_args[] = $arg->getDefaultValue(); + } + } + + foreach ($request as $key => $value) { + if (substr($key, 0, 3) == 'arg') { + $key = str_replace('arg', '', $key); + $calling_args[$key] = $value; + } + } + + // Sort arguments by key -- @see ZF-2279 + ksort($calling_args); + + $result = false; + if (count($calling_args) < count($func_args)) { + $result = $this->fault(new Rest_Exception('Invalid Method Call to ' . $this->_method . '. Requires ' . count($func_args) . ', ' . count($calling_args) . ' given.'), 400); + } + + if (!$result && $this->_functions[$this->_method] instanceof Rest_Reflection_Method) { + // Get class + $class = $this->_functions[$this->_method]->getDeclaringClass()->getName(); + + if ($this->_functions[$this->_method]->isStatic()) { + // for some reason, invokeArgs() does not work the same as + // invoke(), and expects the first argument to be an object. + // So, using a callback if the method is static. + $result = $this->_callStaticMethod($class, $calling_args); + } else { + // Object method + $result = $this->_callObjectMethod($class, $calling_args); + } + } elseif (!$result) { + try { + $result = call_user_func_array($this->_functions[$this->_method]->getName(), $calling_args); //$this->_functions[$this->_method]->invokeArgs($calling_args); + } catch (Exception $e) { + $result = $this->fault($e); + } + } + } else { + + $result = $this->fault( + new Rest_Exception("Unknown Method '$this->_method'."), + 404 + ); + } + } else { + $result = $this->fault( + new Rest_Exception("Unknown Method '$this->_method'."), + 404 + ); + } + } else { + $result = $this->fault( + new Rest_Exception("No Method Specified."), + 404 + ); + } + + if ($result instanceof SimpleXMLElement) { + $response = $result->asXML(); + } elseif ($result instanceof DOMDocument) { + $response = $result->saveXML(); + } elseif ($result instanceof DOMNode) { + $response = $result->ownerDocument->saveXML($result); + } elseif (is_array($result) || is_object($result)) { + $response = $this->_handleStruct($result); + } else { + $response = $this->_handleScalar($result); + } + + if (!$this->returnResponse()) { + if (!headers_sent()) { + foreach ($this->_headers as $header) { + header($header); + } + } + + echo $response; + return; + } + + return $response; + } + + /** + * Implement Rest_Interface::setClass() + * + * @param string $classname Class name + * @param string $namespace Class namespace (unused) + * @param array $argv An array of Constructor Arguments + */ + public function setClass($classname, $namespace = '', $argv = array()) + { + $this->_args = $argv; + foreach ($this->_reflection->reflectClass($classname, $argv)->getMethods() as $method) { + $this->_functions[$method->getName()] = $method; + } + } + + /** + * Handle an array or object result + * + * @param array|object $struct Result Value + * @return string XML Response + */ + protected function _handleStruct($struct) + { + $function = $this->_functions[$this->_method]; + if ($function instanceof Rest_Reflection_Method) { + $class = $function->getDeclaringClass()->getName(); + } else { + $class = false; + } + + $method = $function->getName(); + + $dom = new DOMDocument('1.0', $this->getEncoding()); + if ($class) { + $root = $dom->createElement($class); + $method = $dom->createElement($method); + $root->appendChild($method); + } else { + $root = $dom->createElement($method); + $method = $root; + } + $root->setAttribute('generator', 'Knowledgetree'); + $root->setAttribute('version', '1.0'); + $dom->appendChild($root); + + $this->_structValue($struct, $dom, $method); + + $struct = (array) $struct; + if (!isset($struct['status'])) { + $status = $dom->createElement('status', 'success'); + $method->appendChild($status); + } + + return $dom->saveXML(); + } + + /** + * Recursively iterate through a struct + * + * Recursively iterates through an associative array or object's properties + * to build XML response. + * + * @param mixed $struct + * @param DOMDocument $dom + * @param DOMElement $parent + * @return void + */ + protected function _structValue($struct, DOMDocument $dom, DOMElement $parent) + { + $struct = (array) $struct; + + foreach ($struct as $key => $value) { + if ($value === false) { + $value = 0; + } elseif ($value === true) { + $value = 1; + } + + if (ctype_digit((string) $key)) { + $key = 'key_' . $key; + } + + if (is_array($value) || is_object($value)) { + $element = $dom->createElement($key); + $this->_structValue($value, $dom, $element); + } else { + $element = $dom->createElement($key); + $element->appendChild($dom->createTextNode($value)); + } + + $parent->appendChild($element); + } + } + + /** + * Handle a single value + * + * @param string|int|boolean $value Result value + * @return string XML Response + */ + protected function _handleScalar($value) + { + $function = $this->_functions[$this->_method]; + if ($function instanceof Rest_Reflection_Method) { + $class = $function->getDeclaringClass()->getName(); + } else { + $class = false; + } + + $method = $function->getName(); + + $dom = new DOMDocument('1.0', $this->getEncoding()); + if ($class) { + $xml = $dom->createElement($class); + $methodNode = $dom->createElement($method); + $xml->appendChild($methodNode); + } else { + $xml = $dom->createElement($method); + $methodNode = $xml; + } + $xml->setAttribute('generator', 'KnowledgeTree'); + $xml->setAttribute('version', '1.0'); + $dom->appendChild($xml); + + if ($value === false) { + $value = 0; + } elseif ($value === true) { + $value = 1; + } + + if (isset($value)) { + $element = $dom->createElement('response'); + $element->appendChild($dom->createTextNode($value)); + $methodNode->appendChild($element); + } else { + $methodNode->appendChild($dom->createElement('response')); + } + + $methodNode->appendChild($dom->createElement('status', 'success')); + + return $dom->saveXML(); + } + + /** + * Implement Rest_Interface::fault() + * + * Creates XML error response, returning DOMDocument with response. + * + * @param string|Exception $fault Message + * @param int $code Error Code + * @return DOMDocument + */ + public function fault($exception = null, $code = null) + { + if (isset($this->_functions[$this->_method])) { + $function = $this->_functions[$this->_method]; + } elseif (isset($this->_method)) { + $function = $this->_method; + } else { + $function = 'rest'; + } + + if ($function instanceof Rest_Reflection_Method) { + $class = $function->getDeclaringClass()->getName(); + } else { + $class = false; + } + + if ($function instanceof Rest_Reflection_Function_Abstract) { + $method = $function->getName(); + } else { + $method = $function; + } + + $dom = new DOMDocument('1.0', $this->getEncoding()); + if ($class) { + $xml = $dom->createElement($class); + $xmlMethod = $dom->createElement($method); + $xml->appendChild($xmlMethod); + } else { + $xml = $dom->createElement($method); + $xmlMethod = $xml; + } + $xml->setAttribute('generator', 'KnowledgeTree'); + $xml->setAttribute('version', '1.0'); + $dom->appendChild($xml); + + $xmlResponse = $dom->createElement('response'); + $xmlMethod->appendChild($xmlResponse); + + if ($exception instanceof Exception) { + $element = $dom->createElement('message'); + $element->appendChild($dom->createTextNode($exception->getMessage())); + $xmlResponse->appendChild($element); + $code = $exception->getCode(); + } elseif (($exception !== null) || 'rest' == $function) { + $xmlResponse->appendChild($dom->createElement('message', 'An unknown error occured. Please try again.')); + } else { + $xmlResponse->appendChild($dom->createElement('message', 'Call to ' . $method . ' failed.')); + } + + $xmlMethod->appendChild($xmlResponse); + $xmlMethod->appendChild($dom->createElement('status', 'failed')); + + // Headers to send + if ($code === null || (404 != $code)) { + $this->_headers[] = 'HTTP/1.0 400 Bad Request'; + } else { + $this->_headers[] = 'HTTP/1.0 404 File Not Found'; + } + + return $dom; + } + + /** + * Retrieve any HTTP extra headers set by the server + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Implement Rest_Interface::addFunction() + * + * @param string $function Function Name + * @param string $namespace Function namespace (unused) + */ + public function addFunction($function, $namespace = '') + { + if (!is_array($function)) { + $function = (array) $function; + } + + foreach ($function as $func) { + if (is_callable($func) && !in_array($func, self::$magicMethods)) { + $this->_functions[$func] = $this->_reflection->reflectFunction($func); + } else { + throw new Rest_Exception("Invalid Method Added to Service."); + } + } + } + + /** + * Implement Rest_Interface::getFunctions() + * + * @return array An array of Rest_Reflection_Method's + */ + public function getFunctions() + { + return $this->_functions; + } + + /** + * Implement Rest_Interface::loadFunctions() + * + * @todo Implement + * @param array $functions + */ + public function loadFunctions($functions) + { + } + + /** + * Implement Rest_Interface::setPersistence() + * + * @todo Implement + * @param int $mode + */ + public function setPersistence($mode) + { + } + + /** + * Call a static class method and return the result + * + * @param string $class + * @param array $args + * @return mixed + */ + protected function _callStaticMethod($class, array $args) + { + try { + $result = call_user_func_array(array($class, $this->_functions[$this->_method]->getName()), $args); + } catch (Exception $e) { + $result = $this->fault($e); + } + return $result; + } + + /** + * Call an instance method of an object + * + * @param string $class + * @param array $args + * @return mixed + * @throws Rest_Exception For invalid class name + */ + protected function _callObjectMethod($class, array $args) + { + try { + if ($this->_functions[$this->_method]->getDeclaringClass()->getConstructor()) { + $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstanceArgs($this->_args); + } else { + $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstance(); + } + } catch (Exception $e) { + echo $e->getMessage(); + throw new Rest_Exception('Error instantiating class ' . $class . ' to invoke method ' . $this->_functions[$this->_method]->getName(), 500); + } + + try { + $result = $this->_functions[$this->_method]->invokeArgs($object, $args); + } catch (Exception $e) { + $result = $this->fault($e); + } + + return $result; + } +} diff --git a/webservice/classes/rest/method/Callback.php b/webservice/classes/rest/method/Callback.php new file mode 100644 index 0000000..e3b808a --- /dev/null +++ b/webservice/classes/rest/method/Callback.php @@ -0,0 +1,179 @@ +setOptions($options); + } + } + + /** + * Set object state from array of options + * + * @param array $options + * @return Rest_Method_Callback + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set callback class + * + * @param string $class + * @return Rest_Method_Callback + */ + public function setClass($class) + { + if (is_object($class)) { + $class = get_class($class); + } + $this->_class = $class; + return $this; + } + + /** + * Get callback class + * + * @return string|null + */ + public function getClass() + { + return $this->_class; + } + + /** + * Set callback function + * + * @param string $function + * @return Rest_Method_Callback + */ + public function setFunction($function) + { + $this->_function = (string) $function; + $this->setType('function'); + return $this; + } + + /** + * Get callback function + * + * @return null|string + */ + public function getFunction() + { + return $this->_function; + } + + /** + * Set callback class method + * + * @param string $method + * @return Rest_Method_Callback + */ + public function setMethod($method) + { + $this->_method = $method; + return $this; + } + + /** + * Get callback class method + * + * @return null|string + */ + public function getMethod() + { + return $this->_method; + } + + /** + * Set callback type + * + * @param string $type + * @return Rest_Method_Callback + * @throws Exception + */ + public function setType($type) + { + if (!in_array($type, $this->_types)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid method callback type passed to ' . __CLASS__ . '::' . __METHOD__); + } + $this->_type = $type; + return $this; + } + + /** + * Get callback type + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Cast callback to array + * + * @return array + */ + public function toArray() + { + $type = $this->getType(); + $array = array( + 'type' => $type, + ); + if ('function' == $type) { + $array['function'] = $this->getFunction(); + } else { + $array['class'] = $this->getClass(); + $array['method'] = $this->getMethod(); + } + return $array; + } +} diff --git a/webservice/classes/rest/method/Definition.php b/webservice/classes/rest/method/Definition.php new file mode 100644 index 0000000..17720e5 --- /dev/null +++ b/webservice/classes/rest/method/Definition.php @@ -0,0 +1,267 @@ +setOptions($options); + } + } + + /** + * Set object state from options + * + * @param array $options + * @return Rest_Method_Definition + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set method name + * + * @param string $name + * @return Rest_Method_Definition + */ + public function setName($name) + { + $this->_name = (string) $name; + return $this; + } + + /** + * Get method name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Set method callback + * + * @param array|Rest_Method_Callback $callback + * @return Rest_Method_Definition + */ + public function setCallback($callback) + { + if (is_array($callback)) { + $callback = new Rest_Method_Callback($callback); + } elseif (!$callback instanceof Rest_Method_Callback) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid method callback provided'); + } + $this->_callback = $callback; + return $this; + } + + /** + * Get method callback + * + * @return Rest_Method_Callback + */ + public function getCallback() + { + return $this->_callback; + } + + /** + * Add prototype to method definition + * + * @param array|Rest_Method_Prototype $prototype + * @return Rest_Method_Definition + */ + public function addPrototype($prototype) + { + if (is_array($prototype)) { + require_once 'Prototype.php'; + $prototype = new Rest_Method_Prototype($prototype); + } elseif (!$prototype instanceof Rest_Method_Prototype) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid method prototype provided'); + } + $this->_prototypes[] = $prototype; + return $this; + } + + /** + * Add multiple prototypes at once + * + * @param array $prototypes Array of Rest_Method_Prototype objects or arrays + * @return Rest_Method_Definition + */ + public function addPrototypes(array $prototypes) + { + foreach ($prototypes as $prototype) { + $this->addPrototype($prototype); + } + return $this; + } + + /** + * Set all prototypes at once (overwrites) + * + * @param array $prototypes Array of Rest_Method_Prototype objects or arrays + * @return Rest_Method_Definition + */ + public function setPrototypes(array $prototypes) + { + $this->_prototypes = array(); + $this->addPrototypes($prototypes); + return $this; + } + + /** + * Get all prototypes + * + * @return array $prototypes Array of Rest_Method_Prototype objects or arrays + */ + public function getPrototypes() + { + return $this->_prototypes; + } + + /** + * Set method help + * + * @param string $methodHelp + * @return Rest_Method_Definition + */ + public function setMethodHelp($methodHelp) + { + $this->_methodHelp = (string) $methodHelp; + return $this; + } + + /** + * Get method help + * + * @return string + */ + public function getMethodHelp() + { + return $this->_methodHelp; + } + + /** + * Set object to use with method calls + * + * @param object $object + * @return Rest_Method_Definition + */ + public function setObject($object) + { + if (!is_object($object) && (null !== $object)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid object passed to ' . __CLASS__ . '::' . __METHOD__); + } + $this->_object = $object; + return $this; + } + + /** + * Get object to use with method calls + * + * @return null|object + */ + public function getObject() + { + return $this->_object; + } + + /** + * Set invoke arguments + * + * @param array $invokeArguments + * @return Rest_Method_Definition + */ + public function setInvokeArguments(array $invokeArguments) + { + $this->_invokeArguments = $invokeArguments; + return $this; + } + + /** + * Retrieve invoke arguments + * + * @return array + */ + public function getInvokeArguments() + { + return $this->_invokeArguments; + } + + /** + * Serialize to array + * + * @return array + */ + public function toArray() + { + $prototypes = $this->getPrototypes(); + $signatures = array(); + foreach ($prototypes as $prototype) { + $signatures[] = $prototype->toArray(); + } + + return array( + 'name' => $this->getName(), + 'callback' => $this->getCallback()->toArray(), + 'prototypes' => $signatures, + 'methodHelp' => $this->getMethodHelp(), + 'invokeArguments' => $this->getInvokeArguments(), + 'object' => $this->getObject(), + ); + } +} diff --git a/webservice/classes/rest/method/Parameter.php b/webservice/classes/rest/method/Parameter.php new file mode 100644 index 0000000..6abaf20 --- /dev/null +++ b/webservice/classes/rest/method/Parameter.php @@ -0,0 +1,191 @@ +setOptions($options); + } + } + + /** + * Set object state from array of options + * + * @param array $options + * @return Rest_Method_Parameter + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set default value + * + * @param mixed $defaultValue + * @return Rest_Method_Parameter + */ + public function setDefaultValue($defaultValue) + { + $this->_defaultValue = $defaultValue; + return $this; + } + + /** + * Retrieve default value + * + * @return mixed + */ + public function getDefaultValue() + { + return $this->_defaultValue; + } + + /** + * Set description + * + * @param string $description + * @return Rest_Method_Parameter + */ + public function setDescription($description) + { + $this->_description = (string) $description; + return $this; + } + + /** + * Retrieve description + * + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + /** + * Set name + * + * @param string $name + * @return Rest_Method_Parameter + */ + public function setName($name) + { + $this->_name = (string) $name; + return $this; + } + + /** + * Retrieve name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Set optional flag + * + * @param bool $flag + * @return Rest_Method_Parameter + */ + public function setOptional($flag) + { + $this->_optional = (bool) $flag; + return $this; + } + + /** + * Is the parameter optional? + * + * @return bool + */ + public function isOptional() + { + return $this->_optional; + } + + /** + * Set parameter type + * + * @param string $type + * @return Rest_Method_Parameter + */ + public function setType($type) + { + $this->_type = (string) $type; + return $this; + } + + /** + * Retrieve parameter type + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Cast to array + * + * @return array + */ + public function toArray() + { + return array( + 'type' => $this->getType(), + 'name' => $this->getName(), + 'optional' => $this->isOptional(), + 'defaultValue' => $this->getDefaultValue(), + 'description' => $this->getDescription(), + ); + } +} diff --git a/webservice/classes/rest/method/Prototype.php b/webservice/classes/rest/method/Prototype.php new file mode 100644 index 0000000..ca67d8e --- /dev/null +++ b/webservice/classes/rest/method/Prototype.php @@ -0,0 +1,184 @@ +setOptions($options); + } + } + + /** + * Set return value + * + * @param string $returnType + * @return Rest_Method_Prototype + */ + public function setReturnType($returnType) + { + $this->_returnType = $returnType; + return $this; + } + + /** + * Retrieve return type + * + * @return string + */ + public function getReturnType() + { + return $this->_returnType; + } + + /** + * Add a parameter + * + * @param string $parameter + * @return Rest_Method_Prototype + */ + public function addParameter($parameter) + { + if ($parameter instanceof Rest_Method_Parameter) { + $this->_parameters[] = $parameter; + if (null !== ($name = $parameter->getName())) { + $this->_parameterNameMap[$name] = count($this->_parameters) - 1; + } + } else { + $parameter = new Rest_Method_Parameter(array( + 'type' => (string) $parameter, + )); + $this->_parameters[] = $parameter; + } + return $this; + } + + /** + * Add parameters + * + * @param array $parameter + * @return Rest_Method_Prototype + */ + public function addParameters(array $parameters) + { + foreach ($parameters as $parameter) { + $this->addParameter($parameter); + } + return $this; + } + + /** + * Set parameters + * + * @param array $parameters + * @return Rest_Method_Prototype + */ + public function setParameters(array $parameters) + { + $this->_parameters = array(); + $this->_parameterNameMap = array(); + $this->addParameters($parameters); + return $this; + } + + /** + * Retrieve parameters as list of types + * + * @return array + */ + public function getParameters() + { + $types = array(); + foreach ($this->_parameters as $parameter) { + $types[] = $parameter->getType(); + } + return $types; + } + + /** + * Get parameter objects + * + * @return array + */ + public function getParameterObjects() + { + return $this->_parameters; + } + + /** + * Retrieve a single parameter by name or index + * + * @param string|int $index + * @return null Rest_Server_Method_Parameter + */ + public function getParameter($index) + { + if (!is_string($index) && !is_numeric($index)) { + return null; + } + if (array_key_exists($index, $this->_parameterNameMap)) { + $index = $this->_parameterNameMap[$index]; + } + if (array_key_exists($index, $this->_parameters)) { + return $this->_parameters[$index]; + } + return null; + } + + /** + * Set object state from array + * + * @param array $options + * @return Rest_Method_Prototype + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Serialize to array + * + * @return array + */ + public function toArray() + { + return array( + 'returnType' => $this->getReturnType(), + 'parameters' => $this->getParameters(), + ); + } +} diff --git a/webservice/classes/rest/model/RestService.class.php b/webservice/classes/rest/model/RestService.class.php new file mode 100755 index 0000000..50b2ac1 --- /dev/null +++ b/webservice/classes/rest/model/RestService.class.php @@ -0,0 +1,162 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ + + +/** + * Class Service - will act as a switch between + * SOAP requests and REST requests + *@author KnowledgeTree Team + *@package Webservice + *@version Version 0.9 + */ +class RestService +{ + /** + * Class construct + * + * @param object_type $object + * @return jason encoded string + */ + public function __construct($object) + { + try { + + $result = $this->runAsService($object); + echo jason_encode(array('status_code' => 0,'result' => $result)); + + } catch (Exception $e) { + echo json_encode(array('status_code' => 1,'result' => $e->getMessage())); + } + } + + /** + * Constructor for invoking a reflection object + * Initiates the object as a service + * @param class $object + * @access private + * @return class instance + */ + + public function getJason() + { + $result = $this->runAsService($object); + return $result; + + } + + private function runAsService($object) + { + + if (!isset($_GET['class'])) { + + throw new Exception('Method name not specified.'); + } + + $reflObject = new ReflectionObject($object); + + if (!$reflObject->hasMethod($_GET['class'])) { + + throw new Exception('There is no method with this name.'); + + } + + $reflMethod = $reflObject->getMethod($_GET['method']); + + if ( !$reflMethod->isPublic() || $reflMethod->isStatic() || $reflMethod->isInternal() ) { + + throw new Exception('Invalid method name specified.'); + + } + + $reflParameters = $reflMethod->getParameters(); + + $args = array(); + + + foreach ($reflParameters as $param) { + + $paramName = $param->getName(); + + if (!isset($_GET[$paramName])) { + + if ($param->isDefaultValueAvailable()) { + + $paramValue = $param->getDefaultValue(); + + } else { + + throw new Exception('Required parameter "'.$paramName.'" is not specified.'); + + } + + } else { + + $paramValue = $_GET[$paramName]; + + } + + + if ($param->getClass()) { + + throw new Exception('The method contains unsupported parameter type: class object.'); + + } + + + if ($param->isArray() && !is_array($paramValue)) { + + throw new Exception('Array expected for parameter "'.$paramName.'", but scalar found.'); + + } + + + $args[$param->getPosition()] = $paramValue; + + } + + return $reflMethod->invokeArgs($object, $args); + + } + +} + +?> \ No newline at end of file diff --git a/webservice/classes/rest/model/address.class.php b/webservice/classes/rest/model/address.class.php new file mode 100755 index 0000000..116069d --- /dev/null +++ b/webservice/classes/rest/model/address.class.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/webservice/classes/rest/model/contact.class.php b/webservice/classes/rest/model/contact.class.php new file mode 100755 index 0000000..74e0ae8 --- /dev/null +++ b/webservice/classes/rest/model/contact.class.php @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/webservice/classes/rest/model/contactManager.class.php b/webservice/classes/rest/model/contactManager.class.php new file mode 100755 index 0000000..53b8003 --- /dev/null +++ b/webservice/classes/rest/model/contactManager.class.php @@ -0,0 +1,61 @@ +address = new Address(); + $contact->address->city ="sesamcity"; + $contact->address->street ="sesamstreet"; + $contact->email = "me@you.com"; + $contact->id = 1; + $contact->name ="me"; + + $ret[] = $contact; + return $ret; + } + + /** + * Gets the contact with the given id. + * @param int The id + * @return contact + */ + public function getContact($id) { + //get contact from db + //might wanna throw an exception when it does not exists + throw new Exception("Contact '$id' not found"); + } + /** + * Generates an new, empty contact template + * @return contact + */ + public function newContact() { + return new contact(); + } + + /** + * Saves a given contact + * @param contact + * @return void + */ + public function saveContact(contact $contact) { + $contact->save(); + } + +} +?> \ No newline at end of file diff --git a/webservice/classes/rest/reflection/Class.php b/webservice/classes/rest/reflection/Class.php new file mode 100644 index 0000000..fe7dec6 --- /dev/null +++ b/webservice/classes/rest/reflection/Class.php @@ -0,0 +1,171 @@ +_reflection = $reflection; + $this->setNamespace($namespace); + + foreach ($reflection->getMethods() as $method) { + // Don't aggregate magic methods + if ('__' == substr($method->getName(), 0, 2)) { + continue; + } + + if ($method->isPublic()) { + // Get signatures and description + $this->_methods[] = new Rest_Reflection_Method($this, $method, $this->getNamespace(), $argv); + } + } + } + + /** + * Proxy reflection calls + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if (method_exists($this->_reflection, $method)) { + return call_user_func_array(array($this->_reflection, $method), $args); + } + + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid reflection method'); + } + + /** + * Retrieve configuration parameters + * + * Values are retrieved by key from {@link $_config}. Returns null if no + * value found. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (isset($this->_config[$key])) { + return $this->_config[$key]; + } + + return null; + } + + /** + * Set configuration parameters + * + * Values are stored by $key in {@link $_config}. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->_config[$key] = $value; + } + + /** + * Return array of dispatchable {@link Rest_Reflection_Method}s. + * + * @access public + * @return array + */ + public function getMethods() + { + return $this->_methods; + } + + /** + * Get namespace for this class + * + * @return string + */ + public function getNamespace() + { + return $this->_namespace; + } + + /** + * Set namespace for this class + * + * @param string $namespace + * @return void + */ + public function setNamespace($namespace) + { + if (empty($namespace)) { + $this->_namespace = ''; + return; + } + + if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid namespace'); + } + + $this->_namespace = $namespace; + } + + /** + * Wakeup from serialization + * + * Reflection needs explicit instantiation to work correctly. Re-instantiate + * reflection object on wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_reflection = new ReflectionClass($this->getName()); + } +} diff --git a/webservice/classes/rest/reflection/Function.php b/webservice/classes/rest/reflection/Function.php new file mode 100644 index 0000000..e818ef8 --- /dev/null +++ b/webservice/classes/rest/reflection/Function.php @@ -0,0 +1,15 @@ +_classReflection = $class; + $this->_reflection = $r; + + $classNamespace = $class->getNamespace(); + + // Determine namespace + if (!empty($namespace)) { + $this->setNamespace($namespace); + } elseif (!empty($classNamespace)) { + $this->setNamespace($classNamespace); + } + + // Determine arguments + if (is_array($argv)) { + $this->_argv = $argv; + } + + // If method call, need to store some info on the class + $this->_class = $class->getName(); + + // Perform some introspection + $this->_reflect(); + } + + /** + * Return the reflection for the class that defines this method + * + * @return Rest_Reflection_Class + */ + public function getDeclaringClass() + { + return $this->_classReflection; + } + + /** + * Wakeup from serialization + * + * Reflection needs explicit instantiation to work correctly. Re-instantiate + * reflection object on wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_classReflection = new Rest_Reflection_Class(new ReflectionClass($this->_class), $this->getNamespace(), $this->getInvokeArguments()); + $this->_reflection = new ReflectionMethod($this->_classReflection->getName(), $this->getName()); + } + +} diff --git a/webservice/classes/rest/reflection/Node.php b/webservice/classes/rest/reflection/Node.php new file mode 100644 index 0000000..4cb7236 --- /dev/null +++ b/webservice/classes/rest/reflection/Node.php @@ -0,0 +1,178 @@ +_value = $value; + if (null !== $parent) { + $this->setParent($parent, true); + } + + return $this; + } + + /** + * Set parent node + * + * @param Rest_Reflection_Node $node + * @param boolean $new Whether or not the child node is newly created + * and should always be attached + * @return void + */ + public function setParent(Rest_Reflection_Node $node, $new = false) + { + $this->_parent = $node; + + if ($new) { + $node->attachChild($this); + return; + } + } + + /** + * Create and attach a new child node + * + * @param mixed $value + * @access public + * @return Rest_Reflection_Node New child node + */ + public function createChild($value) + { + $child = new self($value, $this); + + return $child; + } + + /** + * Attach a child node + * + * @param Rest_Reflection_Node $node + * @return void + */ + public function attachChild(Rest_Reflection_Node $node) + { + $this->_children[] = $node; + + if ($node->getParent() !== $this) { + $node->setParent($this); + } + } + + /** + * Return an array of all child nodes + * + * @return array + */ + public function getChildren() + { + return $this->_children; + } + + /** + * Does this node have children? + * + * @return boolean + */ + public function hasChildren() + { + return count($this->_children) > 0; + } + + /** + * Return the parent node + * + * @return null|Rest_Reflection_Node + */ + public function getParent() + { + return $this->_parent; + } + + /** + * Return the node's current value + * + * @return mixed + */ + public function getValue() + { + return $this->_value; + } + + /** + * Set the node value + * + * @param mixed $value + * @return void + */ + public function setValue($value) + { + $this->_value = $value; + } + + /** + * Retrieve the bottommost nodes of this node's tree + * + * Retrieves the bottommost nodes of the tree by recursively calling + * getEndPoints() on all children. If a child is null, it returns the parent + * as an end point. + * + * @return array + */ + public function getEndPoints() + { + $endPoints = array(); + if (!$this->hasChildren()) { + return $endPoints; + } + + foreach ($this->_children as $child) { + $value = $child->getValue(); + + if (null === $value) { + $endPoints[] = $this; + } elseif ((null !== $value) + && $child->hasChildren()) + { + $childEndPoints = $child->getEndPoints(); + if (!empty($childEndPoints)) { + $endPoints = array_merge($endPoints, $childEndPoints); + } + } elseif ((null !== $value) && !$child->hasChildren()) { + $endPoints[] = $child; + } + } + + return $endPoints; + } +} diff --git a/webservice/classes/rest/reflection/Parameter.php b/webservice/classes/rest/reflection/Parameter.php new file mode 100644 index 0000000..3d8dc01 --- /dev/null +++ b/webservice/classes/rest/reflection/Parameter.php @@ -0,0 +1,138 @@ +_reflection = $r; + $this->setType($type); + $this->setDescription($description); + } + + /** + * Proxy reflection calls + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if (method_exists($this->_reflection, $method)) { + return call_user_func_array(array($this->_reflection, $method), $args); + } + + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid reflection method'); + } + + /** + * Retrieve parameter type + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Set parameter type + * + * @param string|null $type + * @return void + */ + public function setType($type) + { + if (!is_string($type) && (null !== $type)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid parameter type'); + } + + $this->_type = $type; + } + + /** + * Retrieve parameter description + * + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + /** + * Set parameter description + * + * @param string|null $description + * @return void + */ + public function setDescription($description) + { + if (!is_string($description) && (null !== $description)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid parameter description'); + } + + $this->_description = $description; + } + + /** + * Set parameter position + * + * @param int $index + * @return void + */ + public function setPosition($index) + { + $this->_position = (int) $index; + } + + /** + * Return parameter position + * + * @return int + */ + public function getPosition() + { + return $this->_position; + } +} diff --git a/webservice/classes/rest/reflection/Prototype.php b/webservice/classes/rest/reflection/Prototype.php new file mode 100644 index 0000000..1b654b5 --- /dev/null +++ b/webservice/classes/rest/reflection/Prototype.php @@ -0,0 +1,79 @@ +_return = $return; + + if (!is_array($params) && (null !== $params)) { + require_once 'rest/Exception.php'; + throw new Rest_Exception('Invalid parameters'); + } + + if (is_array($params)) { + foreach ($params as $param) { + if (!$param instanceof Rest_Reflection_Parameter) { + require_once 'rest/Exception.php'; + throw new Rest_Exception('One or more params are invalid'); + } + } + } + + $this->_params = $params; + } + + /** + * Retrieve return type + * + * @return string + */ + public function getReturnType() + { + return $this->_return->getType(); + } + + /** + * Retrieve the return value object + * + * @access public + * @return Reflection_ReturnValue + */ + public function getReturnValue() + { + return $this->_return; + } + + /** + * Retrieve method parameters + * + * @return array Array of {@link Reflection_Parameter}s + */ + public function getParameters() + { + return $this->_params; + } +} diff --git a/webservice/classes/rest/reflection/ReturnValue.php b/webservice/classes/rest/reflection/ReturnValue.php new file mode 100644 index 0000000..f6a0411 --- /dev/null +++ b/webservice/classes/rest/reflection/ReturnValue.php @@ -0,0 +1,87 @@ +setType($type); + $this->setDescription($description); + } + + /** + * Retrieve parameter type + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Set parameter type + * + * @param string|null $type + * @return void + */ + public function setType($type) + { + if (!is_string($type) && (null !== $type)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid parameter type'); + } + + $this->_type = $type; + } + + /** + * Retrieve parameter description + * + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + /** + * Set parameter description + * + * @param string|null $description + * @return void + */ + public function setDescription($description) + { + if (!is_string($description) && (null !== $description)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid parameter description'); + } + + $this->_description = $description; + } +} diff --git a/webservice/classes/rest/reflection/function/Abstract.php b/webservice/classes/rest/reflection/function/Abstract.php new file mode 100644 index 0000000..fb65965 --- /dev/null +++ b/webservice/classes/rest/reflection/function/Abstract.php @@ -0,0 +1,479 @@ +_reflection = $r; + + // Determine namespace + if (null !== $namespace){ + $this->setNamespace($namespace); + } + + // Determine arguments + if (is_array($argv)) { + $this->_argv = $argv; + } + + // If method call, need to store some info on the class + if ($r instanceof ReflectionMethod) { + $this->_class = $r->getDeclaringClass()->getName(); + } + + // Perform some introspection + $this->_reflect(); + } + + /** + * Create signature node tree + * + * Recursive method to build the signature node tree. Increments through + * each array in {@link $_sigParams}, adding every value of the next level + * to the current value (unless the current value is null). + * + * @param Rest_Reflection_Node $parent + * @param int $level + * @return void + */ + protected function _addTree(Rest_Reflection_Node $parent, $level = 0) + { + if ($level >= $this->_sigParamsDepth) { + return; + } + + foreach ($this->_sigParams[$level] as $value) { + $node = new Rest_Reflection_Node($value, $parent); + if ((null !== $value) && ($this->_sigParamsDepth > $level + 1)) { + $this->_addTree($node, $level + 1); + } + } + } + + /** + * Build the signature tree + * + * Builds a signature tree starting at the return values and descending + * through each method argument. Returns an array of + * {@link Rest_Reflection_Node}s. + * + * @return array + */ + protected function _buildTree() + { + $returnTree = array(); + foreach ((array) $this->_return as $value) { + $node = new Rest_Reflection_Node($value); + $this->_addTree($node); + $returnTree[] = $node; + } + + return $returnTree; + } + + /** + * Build method signatures + * + * Builds method signatures using the array of return types and the array of + * parameters types + * + * @param array $return Array of return types + * @param string $returnDesc Return value description + * @param array $params Array of arguments (each an array of types) + * @param array $paramDesc Array of parameter descriptions + * @return array + */ + protected function _buildSignatures($return, $returnDesc, $paramTypes, $paramDesc) + { + $this->_return = $return; + $this->_returnDesc = $returnDesc; + $this->_paramDesc = $paramDesc; + $this->_sigParams = $paramTypes; + $this->_sigParamsDepth = count($paramTypes); + $signatureTrees = $this->_buildTree(); + $signatures = array(); + + $endPoints = array(); + foreach ($signatureTrees as $root) { + $tmp = $root->getEndPoints(); + if (empty($tmp)) { + $endPoints = array_merge($endPoints, array($root)); + } else { + $endPoints = array_merge($endPoints, $tmp); + } + } + + foreach ($endPoints as $node) { + if (!$node instanceof Rest_Reflection_Node) { + continue; + } + + $signature = array(); + do { + array_unshift($signature, $node->getValue()); + $node = $node->getParent(); + } while ($node instanceof Rest_Reflection_Node); + + $signatures[] = $signature; + } + + // Build prototypes + $params = $this->_reflection->getParameters(); + foreach ($signatures as $signature) { + $return = new Rest_Reflection_ReturnValue(array_shift($signature), $this->_returnDesc); + $tmp = array(); + foreach ($signature as $key => $type) { + $param = new Rest_Reflection_Parameter($params[$key], $type, $this->_paramDesc[$key]); + $param->setPosition($key); + $tmp[] = $param; + } + + $this->_prototypes[] = new Rest_Reflection_Prototype($return, $tmp); + } + } + + /** + * Use code reflection to create method signatures + * + * Determines the method help/description text from the function DocBlock + * comment. Determines method signatures using a combination of + * ReflectionFunction and parsing of DocBlock @param and @return values. + * + * @param ReflectionFunction $function + * @return array + */ + protected function _reflect() + { + $function = $this->_reflection; + $helpText = ''; + $signatures = array(); + $returnDesc = ''; + $paramCount = $function->getNumberOfParameters(); + $paramCountRequired = $function->getNumberOfRequiredParameters(); + $parameters = $function->getParameters(); + $docBlock = $function->getDocComment(); + + if (!empty($docBlock)) { + // Get help text + if (preg_match(':/\*\*\s*\r?\n\s*\*\s(.*?)\r?\n\s*\*(\s@|/):s', $docBlock, $matches)) + { + $helpText = $matches[1]; + $helpText = preg_replace('/(^\s*\*\s)/m', '', $helpText); + $helpText = preg_replace('/\r?\n\s*\*\s*(\r?\n)*/s', "\n", $helpText); + $helpText = trim($helpText); + } + + // Get return type(s) and description + $return = 'void'; + if (preg_match('/@return\s+(\S+)/', $docBlock, $matches)) { + $return = explode('|', $matches[1]); + if (preg_match('/@return\s+\S+\s+(.*?)(@|\*\/)/s', $docBlock, $matches)) + { + $value = $matches[1]; + $value = preg_replace('/\s?\*\s/m', '', $value); + $value = preg_replace('/\s{2,}/', ' ', $value); + $returnDesc = trim($value); + } + } + + // Get param types and description + if (preg_match_all('/@param\s+([^\s]+)/m', $docBlock, $matches)) { + $paramTypesTmp = $matches[1]; + if (preg_match_all('/@param\s+\S+\s+(\$^\S+)\s+(.*?)(@|\*\/)/s', $docBlock, $matches)) + { + $paramDesc = $matches[2]; + foreach ($paramDesc as $key => $value) { + $value = preg_replace('/\s?\*\s/m', '', $value); + $value = preg_replace('/\s{2,}/', ' ', $value); + $paramDesc[$key] = trim($value); + } + } + } + } else { + $helpText = $function->getName(); + $return = 'void'; + } + + // Set method description + $this->setDescription($helpText); + + // Get all param types as arrays + if (!isset($paramTypesTmp) && (0 < $paramCount)) { + $paramTypesTmp = array_fill(0, $paramCount, 'mixed'); + } elseif (!isset($paramTypesTmp)) { + $paramTypesTmp = array(); + } elseif (count($paramTypesTmp) < $paramCount) { + $start = $paramCount - count($paramTypesTmp); + for ($i = $start; $i < $paramCount; ++$i) { + $paramTypesTmp[$i] = 'mixed'; + } + } + + // Get all param descriptions as arrays + if (!isset($paramDesc) && (0 < $paramCount)) { + $paramDesc = array_fill(0, $paramCount, ''); + } elseif (!isset($paramDesc)) { + $paramDesc = array(); + } elseif (count($paramDesc) < $paramCount) { + $start = $paramCount - count($paramDesc); + for ($i = $start; $i < $paramCount; ++$i) { + $paramDesc[$i] = ''; + } + } + + if (count($paramTypesTmp) != $paramCount) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception( + 'Variable number of arguments is not supported for services (except optional parameters). ' + . 'Number of function arguments must currespond to actual number of arguments described in a docblock.'); + } + + $paramTypes = array(); + foreach ($paramTypesTmp as $i => $param) { + $tmp = explode('|', $param); + if ($parameters[$i]->isOptional()) { + array_unshift($tmp, null); + } + $paramTypes[] = $tmp; + } + + $this->_buildSignatures($return, $returnDesc, $paramTypes, $paramDesc); + } + + + /** + * Proxy reflection calls + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if (method_exists($this->_reflection, $method)) { + return call_user_func_array(array($this->_reflection, $method), $args); + } + + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid reflection method ("' .$method. '")'); + } + + /** + * Retrieve configuration parameters + * + * Values are retrieved by key from {@link $_config}. Returns null if no + * value found. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (isset($this->_config[$key])) { + return $this->_config[$key]; + } + + return null; + } + + /** + * Set configuration parameters + * + * Values are stored by $key in {@link $_config}. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->_config[$key] = $value; + } + + /** + * Set method's namespace + * + * @param string $namespace + * @return void + */ + public function setNamespace($namespace) + { + if (empty($namespace)) { + $this->_namespace = ''; + return; + } + + if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid namespace'); + } + + $this->_namespace = $namespace; + } + + /** + * Return method's namespace + * + * @return string + */ + public function getNamespace() + { + return $this->_namespace; + } + + /** + * Set the description + * + * @param string $string + * @return void + */ + public function setDescription($string) + { + if (!is_string($string)) { + require_once 'classes/rest/Exception.php'; + throw new Rest_Exception('Invalid description'); + } + + $this->_description = $string; + } + + /** + * Retrieve the description + * + * @return void + */ + public function getDescription() + { + return $this->_description; + } + + /** + * Retrieve all prototypes as array of + * {@link Rest_Reflection_Prototype Rest_Reflection_Prototypes} + * + * @return array + */ + public function getPrototypes() + { + return $this->_prototypes; + } + + /** + * Retrieve additional invocation arguments + * + * @return array + */ + public function getInvokeArguments() + { + return $this->_argv; + } + + /** + * Wakeup from serialization + * + * Reflection needs explicit instantiation to work correctly. Re-instantiate + * reflection object on wakeup. + * + * @return void + */ + public function __wakeup() + { + if ($this->_reflection instanceof ReflectionMethod) { + $class = new ReflectionClass($this->_class); + $this->_reflection = new ReflectionMethod($class->newInstance(), $this->getName()); + } else { + $this->_reflection = new ReflectionFunction($this->getName()); + } + } +} diff --git a/webservice/classes/soap/IPPhpDoc.class.php b/webservice/classes/soap/IPPhpDoc.class.php new file mode 100755 index 0000000..35e49b4 --- /dev/null +++ b/webservice/classes/soap/IPPhpDoc.class.php @@ -0,0 +1,110 @@ +getClasses(); + } + + /** Sets the current class + * @param string The class name + * @return void + */ + public function setClass($class) { + $this->class = new IPReflectionClass($class); + } + /** + * Get all classes + * + * @return IPReflectionClass[] + */ + function getClasses() { + $ar = get_declared_classes(); + foreach($ar as $class){ + $c = new reflectionClass($class); + if($c->isUserDefined()){//add only when class is user-defined + $this->classes[$class] = new IPReflectionClass($class); + } + } + ksort($this->classes); + return $this->classes; + } + /** + * Generates the documentation page with all classes, methods etc. + * @TODO FIXME: use the new template class + * @param string Template file (optional) + * @return string + */ + public function getDocumentation($template="classes/soap/templates/docclass.xsl") { + if(!is_file($template)) + throw new WSException("Could not find the template file: '$template'"); + $xtpl = new IPXSLTemplate($template); + $documentation = Array(); + $documentation['menu'] = Array(); + //loop menu items + $documentation['menu'] = $this->getClasses(); + + if($this->class){ + if($this->class->isUserDefined()) { + $this->class->properties = $this->class->getProperties(false, false); + $this->class->methods = $this->class->getMethods(false, false); + foreach((array)$this->class->methods as $method) { + $method->params = $method->getParameters(); + } + } else { + $documentation['fault'] = "Native class"; + } + $documentation['class'] = $this->class; + } + echo $xtpl->execute($documentation); + } + + + /** + * + * @param $comment String The doccomment + * @param $annotationName String the annotation name + * @param $annotationClass String the annotation class + * @return void + */ + public static function getAnnotation($comment, $annotationName, $annotationClass = null){ + if(!$annotationClass){ + $annotationClass = $annotationName; + } + $start = 0; + if($start = stripos($comment, "@".$annotationName)){ + $obi = new $annotationClass(); + $start = strpos($comment, "(", $start); + $end = strpos($comment, ")", $start); + $propString = substr($comment, $start, ($end-$start) + 1); + $eval = "return Array$propString;"; + $arr = @eval($eval); + if($arr === false) throw new Exception("Error parsing annotation: $propString"); + + foreach ((Array)$arr as $name => $value){ + $obi->$name= $value; + } + return $obi; + } + throw new Exception("Cannot find annotation @$annotationName ($start, $end): {$this->comment} "); + } + +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/IPReflectionClass.class.php b/webservice/classes/soap/IPReflectionClass.class.php new file mode 100755 index 0000000..21e30b9 --- /dev/null +++ b/webservice/classes/soap/IPReflectionClass.class.php @@ -0,0 +1,114 @@ +classname = $classname; + parent::__construct($classname); + + $this->parseComment(); + } + + /** + *Provides all methods exposed by this class as an array + * + * @param boolean If the method should also return protected functions + * @param boolean If the method should also return private functions + * @return IPReflectionMethod[] + */ + public function getMethods($alsoProtected = true, $alsoPrivate = true){ + $ar = parent::getMethods(); + foreach($ar as $method){ + $m = new IPReflectionMethod($this->classname, $method->name); + if((!$m->isPrivate() || $alsoPrivate) && (!$m->isProtected() || $alsoProtected) && ($m->getDeclaringClass()->name == $this->classname)) + $this->methods[$method->name] = $m; + } + ksort($this->methods); + return $this->methods; + } + + /** + * Exposes an array with public properties of the class + * + * @param boolean If the method should also return protected properties + * @param boolean If the method should also return private properties + * @return IPReflectionProperty[] + */ + public function getProperties($alsoProtected=true,$alsoPrivate=true) { + $ar = parent::getProperties(); + $this->properties = Array(); + foreach($ar as $property){ + if((!$property->isPrivate() || $alsoPrivate) && (!$property->isProtected() || $alsoProtected)){ + try{ + $p = new IPReflectionProperty($this->classname, $property->getName()); + $this->properties[$property->name]=$p; + }catch(ReflectionException $exception){ + echo "Fault on property: ".$property->name."
\n"; + } + } + } + ksort($this->properties); + return $this->properties; + } + + /** + * Exposes annotations of the class + * @param $annotationName String the annotation name + * @param $annotationClass String the annotation class + * @return void + */ + public function getAnnotation($annotationName, $annotationClass = null){ + return IPPhpDoc::getAnnotation($this->comment, $annotationName, $annotationClass); + } + + /** + * Gets all the usefull information from the comments + * @return void + */ + private function parseComment() { + $this->comment = $this->getDocComment(); + new IPReflectionCommentParser($this->comment, $this); + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/IPReflectionCommentParser.class.php b/webservice/classes/soap/IPReflectionCommentParser.class.php new file mode 100755 index 0000000..9d8340f --- /dev/null +++ b/webservice/classes/soap/IPReflectionCommentParser.class.php @@ -0,0 +1,170 @@ +comment = $comment; + $this->obj = $obj; + $this->parse(); + } + /** + * parses the comment, line for line + * + * Will take the type of comment (class, property or function) as an + * argument and split it up in lines. + * @param string Defines if its the comment for a class, public of function + * @return void + */ + function parse() { + //reset object + $descriptionDone = false; + $this->fullDescriptionDone = false; + + //split lines + $lines = split("\n", $this->comment); + + //check lines for description or tags + foreach ($lines as $line) { + $pos = strpos($line,"* @"); + if (trim($line) == "/**" || trim($line) == "*/") { //skip the start and end line + }elseif (!($pos === false)) { //comment + $this->parseTagLine(substr($line,$pos+3)); + $descriptionDone=true; + }elseif(!$descriptionDone){ + $this->parseDescription($line); + } + } + //if full description is empty, put small description in full description + if (trim(str_replace(Array("\n","\r"), Array("", ""), $this->obj->fullDescription)) == "") + $this->obj->fullDescription = $this->obj->smallDescription; + } + + /** + * Parses the description to the small and full description properties + * + * @param string The description line + * @return void + */ + + function parseDescription($descriptionLine) { + if(strpos($descriptionLine,"*") <= 2) $descriptionLine = substr($descriptionLine, (strpos($descriptionLine,"*") + 1)); + + //geen lege comment regel indien al in grote omschrijving + if(trim(str_replace(Array("\n","\r"), Array("", ""), $descriptionLine)) == ""){ + if($this->obj->fullDescription == "") + $descriptionLine = ""; + $this->smallDescriptionDone = true; + } + + if(!$this->smallDescriptionDone)//add to small description + $this->obj->smallDescription.=$descriptionLine; + else{//add to full description + $this->obj->fullDescription.=$descriptionLine; + } + } + + /** + * Parses a tag line and extracts the tagname and values + * + * @param string The tagline + * @return void + */ + function parseTagLine($tagLine) { + $tagArr = explode(" ", $tagLine); + $tag = $tagArr[0]; + + switch(strtolower($tag)){ + case 'abstract': + $this->obj->abstract = true; break; + case 'access': + $this->obj->isPrivate = (strtolower(trim($tagArr[1]))=="private")?true:false; + break; + case 'author': + unset($tagArr[0]); + $this->obj->author = implode(" ",$tagArr); + break; + case 'copyright': + unset($tagArr[0]); + $this->obj->copyright = implode(" ",$tagArr); + break; + case 'deprecated': + case 'deprec': + $this->obj->deprecated = true; + break; + case 'extends': break; + case 'global': + $this->obj->globals[] = $tagArr[1]; + break; + case 'param': + $o = new stdClass(); + $o->type = trim($tagArr[1]); + $o->comment = implode(" ",$tagArr); + $this->obj->params[] = $o; + break; + case 'return': + $this->obj->return = trim($tagArr[1]); break; + case 'link':break; + case 'see':break; + case 'since': + $this->obj->since = trim($tagArr[1]); break; + case 'static': + $this->obj->static = true; break; + case 'throws': + unset($tagArr[0]); + $this->obj->throws = implode(" ",$tagArr); + break; + case 'todo': + unset($tagArr[0]); + $this->obj->todo[] = implode(" ",$tagArr); + break; + case 'var': + $this->obj->type = trim($tagArr[1]); + unset($tagArr[0],$tagArr[1]); + $comment=implode(" ",$tagArr); + //check if its an optional property + $this->obj->optional = strpos($comment,"[OPTIONAL]") !== FALSE; + $this->obj->autoincrement = strpos($comment,"[AUTOINCREMENT]") !== FALSE; + $this->obj->description = str_replace("[OPTIONAL]", "", $comment); + break; + case 'version': + $this->obj->version = $tagArr[1]; + break; + default: + //echo "\nno valid tag: '".strtolower($tag)."' at tagline: '$tagLine'
"; + //do nothing + } + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/IPReflectionMethod.class.php b/webservice/classes/soap/IPReflectionMethod.class.php new file mode 100755 index 0000000..9f11b05 --- /dev/null +++ b/webservice/classes/soap/IPReflectionMethod.class.php @@ -0,0 +1,100 @@ +classname = $class; + parent::__construct($class,$method); + $this->parseComment(); + } + + /** + * Returns the full function name, including arguments + * @return string + */ + public function getFullName(){ + $args = $this->getParameters(); + $argstr = ""; + + foreach((array)$args as $arg){ + if($argstr!="")$argstr.=", "; + $argstr.= $arg->type ." $".$arg->name; + } + return $this->return." ".$this->name."(".$argstr.")"; + } + + /** + * Returns an array with parameter objects, containing type info etc. + * + * @return ReflectionParameter[] Associative array with parameter objects + */ + public function getParameters(){ + $this->parameters = Array(); + $ar = parent::getParameters(); + $i = 0; + + foreach((array)$ar as $parameter){ + $parameter->type = $this->params[$i++]->type; + $this->parameters[$parameter->name] = $parameter; + } + + return $this->parameters; + } + + /** + * + * @param $annotationName String the annotation name + * @param $annotationClass String the annotation class + * @return void + */ + public function getAnnotation($annotationName, $annotationClass = null){ + return IPPhpDoc::getAnnotation($this->comment, $annotationName, $annotationClass); + } + + /** + * Parses the comment and adds found properties to this class + * @return void + */ + private function parseComment(){ + $this->comment = $this->getDocComment(); + new IPReflectionCommentParser($this->comment, $this); + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/IPReflectionProperty.class.php b/webservice/classes/soap/IPReflectionProperty.class.php new file mode 100755 index 0000000..4feaa29 --- /dev/null +++ b/webservice/classes/soap/IPReflectionProperty.class.php @@ -0,0 +1,75 @@ +classname = $class; + parent::__construct($class, $property); + $this->parseComment(); + } + + /** + * + * @param $annotationName String the annotation name + * @param $annotationClass String the annotation class + * @return void + */ + public function getAnnotation($annotationName, $annotationClass = null){ + return IPPhpDoc::getAnnotation($this->comment, $annotationName, $annotationClass); + } + + private function parseComment(){ + // No getDocComment available for properties in php 5.0.3 :( + $this->comment = $this->getDocComment(); + new IPReflectionCommentParser($this->comment, $this); + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/IPXMLSchema.class.php b/webservice/classes/soap/IPXMLSchema.class.php new file mode 100755 index 0000000..4b1653f --- /dev/null +++ b/webservice/classes/soap/IPXMLSchema.class.php @@ -0,0 +1,179 @@ +parentElement = $parentElement; + } + + /** + * Ads a complexType tag with xmlschema content to the types tag + * @param string The variable type (Array or class name) + * @param string The variable name + * @param domNode Used when adding an inline complexType + * @return domNode The complexType node + */ + + public function addComplexType($type, $name = false, $parent = false) { + if(!$parent){//outline element + //check if the complexType doesn't already exists + if(isset($this->types[$name])) return $this->types[$name]; + + //create the complexType tag beneath the xsd:schema tag + $complexTypeTag=$this->addElement("xsd:complexType", $this->parentElement); + if($name){//might be an anonymous element + $complexTypeTag->setAttribute("name",$name); + $this->types[$name]=$complexTypeTag; + } + }else{//inline element + $complexTypeTag = $this->addElement("xsd:complexType", $parent); + } + + //check if its an array + if(strtolower(substr($type,0,6)) == 'array(' || substr($type,-2) == '[]'){ + $this->addArray($type,$complexTypeTag); + }else{//it should be an object + $tag=$this->addElement("xsd:all", $complexTypeTag); + //check if it has the name 'object()' (kind of a stdClass) + if(strtolower(substr($type,0,6)) == 'object'){//stdClass + $content = substr($type, 7, (strlen($type)-1)); + $properties = split(",", $content);//split the content into properties + foreach((array)$properties as $property){ + if($pos = strpos($property, "=>")){//array with keys (order is important, so use 'sequence' tag) + $keyType = substr($property,6,($pos-6)); + $valueType = substr($property,($pos+2), (strlen($property)-7)); + $el->$this->addTypeElement($valueType, $keyType, $tag); + }else{ + throw new WSDLException("Error creating WSDL: expected \"=>\". When using the object() as type, use it as object(paramname=>paramtype,paramname2=>paramtype2)"); + } + } + }else{ //should be a known class + + if(!class_exists($name)) throw new WSDLException("Error creating WSDL: no class found with the name '$name' / $type : $parent, so how should we know the structure for this datatype?"); + $v = new IPReflectionClass($name); + //TODO: check if the class extends another class? + $properties = $v->getProperties(false, false);//not protected and private properties + + foreach((array) $properties as $property){ + if(!$property->isPrivate){ + $el = $this->addTypeElement($property->type, $property->name, $tag, $property->optional); + } + } + } + } + return $complexTypeTag; + } + + /** + * Adds an element tag beneath the parent and takes care + * of the type (XMLSchema type or complexType) + * @param string The datatype + * @param string Name of the element + * @param domNode The parent domNode + * @param boolean If the property is optional + * @return domNode + */ + public function addTypeElement($type, $name, $parent, $optional = false) { + $el = $this->addElement("xsd:element", $parent); + $el->setAttribute("name", $name); + + if($optional){//if it's an optional property, set minOccur to 0 + $el->setAttribute("minOccurs", "0"); + $el->setAttribute("maxOccurs", "1"); + } + + //check if XML Schema datatype + if($t = $this->checkSchemaType(strtolower($type))) + $el->setAttribute("type", "xsd:".$t); + else{//no XML Schema datatype + //if valueType==Array, then create anonymouse inline complexType (within element tag without type attribute) + if(substr($type,-2) == '[]'){ + if($this->array_inline){ + $this->addComplexType($type, false, $el); + }else{ + $name = substr($type, 0, -2)."Array"; + $el->setAttribute("type", "tns:".$name); + $this->addComplexType($type, $name, false); + } + }else{//else, new complextype, outline (element with 'ref' attrib) + $el->setAttribute("type", "tns:".$type); + $this->addComplexType($type, $type); + } + } + return $el; + } + + /** + * Creates an xmlSchema element for the given array + */ + public function addArray($type, $parent) { + $cc = $this->addElement("xsd:complexContent", $parent); + $rs = $this->addElement("xsd:restriction", $cc); + $rs->setAttribute("base", "SOAP-ENC:Array"); + + $type = (substr($type,-2) == '[]') ? substr($type, 0, (strlen($type)-2)) : substr($type, 6, (strlen($type)-7)); + $el = $this->addElement("xsd:attribute", $rs); + $el->setAttribute("ref", "SOAP-ENC:arrayType"); + + //check if XML Schema datatype + if($t = $this->checkSchemaType(strtolower($type))) + $el->setAttribute("wsdl:arrayType", "xsd:".$t."[]"); + else{//no XML Schema datatype + //if valueType==Array, then create anonymouse inline complexType (within element tag without type attribute) + if(substr($type,-2) == '[]'){ + $this->addComplexType($type, false, $el); + }else{//else, new complextype, outline (element with 'ref' attrib) + $el->setAttribute("wsdl:arrayType", "tns:".$type."[]"); + $this->addComplexType($type, $type); + } + } + return $el; + } + + /** + * Checks if the given type is a valid XML Schema type or can be casted to a schema type + * @param string The datatype + * @return string + */ + public static function checkSchemaType($type) { + //XML Schema types + $types = Array("string" => "string", + "int" => "int", + "integer" => "int", + "boolean" => "boolean", + "float" => "float"); + if(isset($types[$type])) return $types[$type]; + else return false; + } + + /** + * Adds an child element to the parent + * @param string + * @param domNode + * @return domNode + */ + private function addElement($name, $parent = false, $ns = false) { + if($ns) + $el = $parent->ownerDocument->createElementNS($ns, $name); + else + $el = $parent->ownerDocument->createElement($name); + if($parent) + $parent->appendChild($el); + return $el; + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/IPXSLTemplate.class.php b/webservice/classes/soap/IPXSLTemplate.class.php new file mode 100755 index 0000000..9f21055 --- /dev/null +++ b/webservice/classes/soap/IPXSLTemplate.class.php @@ -0,0 +1,85 @@ +namespace = $namespace; + $this->tagName = $tagName; + $this->function = $function; + } + + public function process($node) { + $x = $node; + eval($this->function); + } +} + + +class IPXSLTemplate { + const NAMESPACE = "http://schema.knowledgetree.com/xmltemplate/"; + public $dom = null; + public $xsltproc = null; + public $customTags = Array(); + + public function __construct($file) { + $this->dom = new DOMDocument(); + $this->dom->load($file); + $this->xsltproc = new XSLTProcessor(); + $this->xsltproc->registerPHPFunctions(); + } + + public function execute($model) { + //process custom tags + foreach($this->customTags as $reg) { + $nodelist = $this->dom->getElementsByTagNameNS($reg->namespace, $reg->tagName); + for($i = $nodelist->length; $i > 0; $i--){ + $reg->process($nodelist->item($i-1)); + } + } + + $this->xsltproc->importStyleSheet($this->dom); + + $modelDom = new DomDocument(); + $modelDom->appendChild($modelDom->createElement("model")); //root node + IPXSLTemplate::makeXML($model, $modelDom->documentElement); + //echo $modelDom->saveXML(); + return $this->xsltproc->transformToXml($modelDom); + } + + /** Add a new custom tag registration */ + public function registerTag($namespace, $tagName, $function) { + $this->customTags[] = new tagRegistration($namespace, $tagName, $function); + } + + /** Makes a XML node from an object/ array / text */ + static function makeXML($model, $parent, $addToParent = false) { + if(is_array($model)){ + foreach($model as $name => $value){ + if(!is_numeric($name)) { + $node = $parent->ownerDocument->createElement($name); + $parent->appendChild($node); + IPXSLTemplate::makeXml($value, $node, true); + } else { + $node = $parent; + IPXSLTemplate::makeXml($value, $node); + } + } + } elseif (is_object($model)) { + if($addToParent) + $node = $parent; + else{ + $node = $parent->ownerDocument->createElement(get_class($model)); + $parent->appendChild($node); + } + foreach($model as $propertyName => $propertyValue){ + $property = $parent->ownerDocument->createElement($propertyName); + $node->appendChild($property); + IPXSLTemplate::makeXml($propertyValue, $property); + } + } else { + $parent->appendChild($parent->ownerDocument->createTextNode($model)); + } + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/WSDLException.class.php b/webservice/classes/soap/WSDLException.class.php new file mode 100755 index 0000000..df20680 --- /dev/null +++ b/webservice/classes/soap/WSDLException.class.php @@ -0,0 +1,26 @@ +msg = $msg; + } + /** + * @return void + */ + function Display() { + print "Error creating WSDL document:".$this->msg; + //var_dump(debug_backtrace()); + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/WSDLStruct.class.php b/webservice/classes/soap/WSDLStruct.class.php new file mode 100755 index 0000000..4b2c2b4 --- /dev/null +++ b/webservice/classes/soap/WSDLStruct.class.php @@ -0,0 +1,338 @@ +use = $use; + $this->binding_style=$type; + $this->tns = $tns; + $this->url = $url; + $this->doc = new domDocument(); + $this->definitions = $this->addElement("wsdl:definitions",$this->doc); + + $this->typesTag = $this->addElement("wsdl:types", $this->definitions); + $this->xsdSchema = $this->addElement("xsd:schema", $this->typesTag); + $this->xsdSchema->setAttribute("targetNamespace", $this->tns); + $this->xmlSchema = new IPXMLSchema($this->xsdSchema); + + } + + /** + * Adds the class to the services for this WSDL + * + * @param IPReflectionClass The service + * @return void + */ + public function setService(IPReflectionClass $class){ + $this->services[$class->classname] = $class; + $this->services[$class->classname]->getMethods(false, false); + } + /** + * @return string The WSDL document for this structure + */ + public function generateDocument(){ + $this->addToDebug("Generating document"); + + //add all definitions + $definitions=$this->definitions; + $definitions->setAttribute("xmlns", self::NS_WSDL); + $definitions->setAttribute("xmlns:soap", self::NS_SOAP); + $definitions->setAttribute("xmlns:SOAP-ENC", self::NS_ENC); + $definitions->setAttribute("xmlns:wsdl", self::NS_WSDL); + $definitions->setAttribute("xmlns:xsd", self::NS_XSD); + $definitions->setAttribute("xmlns:tns", $this->tns); + $definitions->setAttribute("targetNamespace", $this->tns); + + //add all the services + foreach((array)$this->services as $serviceName => $service){ + //add the portType + $portType = $this->addPortType($serviceName); + + //add binding + $binding = $this->addBinding($serviceName); + + //loop the operations + foreach((array)$service->methods as $operation){ + $operationName = $operation->name; + $operationTag = $this->addOperation($operationName, $serviceName); + + //input + //only when to operation needs arguments + $parameters = $operation->getParameters(); + if(count($parameters)>0 || self::CREATE_EMPTY_INPUTS){ + $messageName = $operationName."Request"; + $input=$this->addElement("wsdl:input", $operationTag); + $input->setAttribute("message", "tns:".$messageName); + $para=Array(); + foreach((array)$parameters as $parameterName => $parameter){ + $para[$parameterName] = $parameter->type; + } + $this->addMessage($messageName, $para); + $this->addInput($this->bindingOperationTags[$serviceName][$operationName]); + } + + + //output + //only when the operation returns something + if(!$operation->return || trim($operation->return) == "") throw new WSDLException('No return type for '.$operationName); + if(strtolower(trim($operation->return))!='void'){ + $messageName = $operationName."Response"; + $output = $this->addElement("wsdl:output", $operationTag); + $output->setAttribute("message", "tns:".$messageName); + $this->addOutput($this->bindingOperationTags[$serviceName][$operationName]); + $this->addMessage($messageName,Array($operation->name."Return" => $operation->return)); + } + } + // SH. now add the portType and binding + $this->definitions->AppendChild($portType); + $this->definitions->AppendChild($binding); + + //add the service + $this->addService($serviceName); + + } + return $this->doc->saveXML(); + } + + /** + * Adds a new operation to the given service + * @param string operation name + * @param string service name + * @return domElement + */ + private function addOperation($operationName, $serviceName){ + $this->addToDebug("Adding Operation: '$operationName : $serviceName'"); + $operationTag = $this->addElement("wsdl:operation",$this->portTypeTags[$serviceName]); + $operationTag->setAttribute("name",$operationName); + + //create operation tag for binding + $bindingOperationTag = $this->addElement("wsdl:operation",$this->bindingTags[$serviceName]); + $bindingOperationTag->setAttribute("name",$operationName); + + //soap operation tag + $soapOperationTag = $this->addElement("soap:operation",$bindingOperationTag); + $soapOperationTag->setAttribute("soapAction",$this->url."&method=".$operationName); + $soapOperationTag->setAttribute("style",($this->binding_style == SOAP_RPC)? "rpc" : "document"); + + //save references + $this->operationTags[$serviceName][$operationName] = $operationTag; + $this->bindingOperationTags[$serviceName][$operationName] = $bindingOperationTag; + + //and return + return $operationTag; + } + + /** + * adds a new service tag to the WSDL file + * @param string the service name + * @return domElement + */ + private function addService($serviceName){ + $this->addToDebug("Adding service: '$serviceName'"); + //create service + $serviceTag=$this->addElement("wsdl:service",$this->definitions); + $serviceTag->setAttribute("name",$serviceName); + + //port tag + $portTag=$this->addElement("wsdl:port", $serviceTag); + $portTag->setAttribute("name", $serviceName."Port"); + $portTag->setAttribute("binding", "tns:".$serviceName."Binding"); + + //address tag + $addressTag = $this->addElement("soap:address", $portTag); + $addressTag->setAttribute("location", $this->url); + + //keep a reference + $this->serviceTags[$serviceName] = $serviceTag; + //and return + return $serviceTag; + } + + /** + * Adds a new portType to the WSDL structure + * @param string the service name for which we create a portType + * @return domElement + */ + private function addPortType($serviceName){ + $this->addToDebug("Adding portType: '$serviceName'"); + // SH don't add to main doc just yet + // $portTypeTag=$this->addElement("wsdl:portType", $this->definitions); + $portTypeTag = $this->addElement("wsdl:portType"); + $portTypeTag->setAttribute("name", $serviceName."PortType"); + + //keep a reference + $this->portTypeTags[$serviceName]=$portTypeTag; + //and return + return $portTypeTag; + } + + /** + * Adds a new binding to the WSDL structure + * @param string serviceName to bind + * @return domElement + */ + private function addBinding($serviceName){ + $this->addToDebug("Adding binding: '$serviceName'"); + // SH. don't add to main doc just yet + // $bindingTag=$this->addElement("binding"); + $bindingTag=$this->addElement("binding",$this->definitions); + $bindingTag->setAttribute("name", $serviceName."Binding"); + $bindingTag->setAttribute("type", "tns:".$serviceName."PortType"); + + //soap binding tag + $soapBindingTag = $this->addElement("soap:binding", $bindingTag); + $soapBindingTag->setAttribute("style", ($this->binding_style == SOAP_RPC)? "rpc" : "document"); + $soapBindingTag->setAttribute("transport", "http://schemas.xmlsoap.org/soap/http"); + + //keep a reference + $this->bindingTags[$serviceName] = $bindingTag; + //and return + return $bindingTag; + } + + /** + * Adds a message tag to the WSDL document + * @param string Message name + * @param Array[string=>string] Array with variables & types + */ + private function addMessage($name, $parts){ + $this->addToDebug("Adding message: '$name'"); + $msg = $this->addElement("message", $this->definitions); + $msg->setAttribute("name", $name); + foreach((array)$parts as $partName => $partType){ + $this->addToDebug("Adding Message part: '$partName => $partType'"); + $part=$this->addElement("part", $msg); + $part->setAttribute("name", $partName); + + //check if it is a valid XML Schema datatype + if($t = IPXMLSchema::checkSchemaType(strtolower($partType))) + $part->setAttribute("type", "xsd:".$t); + else{ + //If it is an array, change the type name + $partName = (substr($partType,-2) == "[]")?substr($partType,0,strpos($partType,"["))."Array":$partType; + + $part->setAttribute("type", "tns:".$partName); + $this->xmlSchema->addComplexType($partType, $partName); + } + } + } + + /** + * Adds an input element to the given parent (an operation tag) + * @param domNode The Parent domNode + * @param boolean Kind of tag. true=input tag, false=output tag + * @return domNode The input/output node + */ + private function addInput($parent, $input=true){ + $name = $input ? "wsdl:input" : "wsdl:output"; + $tag=$this->addElement($name, $parent); + $soapOperation=$this->addElement("soap:body", $tag); + $soapOperation->setAttribute("use", ($this->use == SOAP_ENCODED)? "encoded" : "literal"); + $soapOperation->setAttribute("namespace", $this->tns); + if($this->use == SOAP_ENCODED) + $soapOperation->setAttribute("encodingStyle", self::NS_ENC); + } + + /** + * Adds an output element to the given parent (an operation tag) + * @param domNode The Parent domNode + * @return domNode The output node + */ + private function addOutput($parent){ + return $this->addInput($parent,false); + } + + /************************* Supporting functions ****************************/ + + private function addToDebug($msg){ + if($this->_debug) echo '-'.$msg."
\n"; + } + + /** + * Adds an child element to the parent + * @param string The name element + * @param domNode + * @return domNode + */ + private function addElement($name, $parent=false, $ns=false){ + if($ns) + $el=$this->doc->createElementNS($ns,$name); + else + $el=$this->doc->createElement($name); + if($parent) + $parent->appendChild($el); + return $el; + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/WSException.class.php b/webservice/classes/soap/WSException.class.php new file mode 100755 index 0000000..93c4de4 --- /dev/null +++ b/webservice/classes/soap/WSException.class.php @@ -0,0 +1,24 @@ +msg = $msg; + } + /** + * @return void + */ + public function Display() { + echo $this->msg; + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/WSHelper.class.php b/webservice/classes/soap/WSHelper.class.php new file mode 100755 index 0000000..a7e82cb --- /dev/null +++ b/webservice/classes/soap/WSHelper.class.php @@ -0,0 +1,183 @@ +uri = $uri; + $this->setWSDLCacheFolder($_SERVER['DOCUMENT_ROOT'].dirname($_SERVER['PHP_SELF'])."/wsdl/"); + if($class) $this->setClass($class); + } + + /** + * Adds the given class name to the list of classes + * to be included in the documentation/WSDL/Request handlers + * @param string + * @return void + */ + public function setClass($name){ + $this->name = $name; + $this->wsdlfile = $this->wsdlFolder.$this->name.".wsdl"; + } + + public function setWSDLCacheFolder($folder) { + $this->wsdlFolder = $folder; + //reset wsdlfile + $this->wsdlfile = $this->wsdlFolder.$this->name.".wsdl"; + } + /** + * Sets the persistence level for the soap class + */ + public function setPersistence($persistence) { + $this->persistence = $persistence; + } + + /** + * Handles everything. Makes sure the webservice is handled, + * documentations is generated, or the wsdl is generated, + * according to the page request + * @return void + */ + public function handle(){ + if(substr($_SERVER['QUERY_STRING'], -4) == 'wsdl'){ + $this->showWSDL(); + }elseif(isset($GLOBALS['HTTP_RAW_POST_DATA']) && strlen($GLOBALS['HTTP_RAW_POST_DATA'])>0){ + $this->handleRequest(); + }else{ + $this->createDocumentation(); + } + } + /** + * Checks if the current WSDL is up-to-date, regenerates if necessary and outputs the WSDL + * @return void + */ + public function showWSDL(){ + //check if it's a legal webservice class + if(!in_array($this->name, $this->classNameArr)) + throw new Exception("No valid webservice class."); + + //@TODO: nog een mooie oplossing voor het cachen zoeken + header("Content-type: text/xml"); + if($this->useWSDLCache && file_exists($this->wsdlfile)){ + readfile($this->wsdlfile); + }else{ + //make sure to refresh PHP WSDL cache system + ini_set("soap.wsdl_cache_enabled",0); + echo $this->createWSDL(); + } + } + + private function createWSDL(){ + $this->class = new IPReflectionClass($this->name); + $wsdl = new WSDLStruct($this->uri, "http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."?class=".$this->name, $this->type, $this->use); + $wsdl->setService($this->class); + + try { + $gendoc = $wsdl->generateDocument(); + } catch (WSDLException $exception) { + $exception->Display(); + exit(); + } + + $fh = fopen($this->wsdlfile, "w+"); + fwrite($fh, $gendoc); + fclose($fh); + + return $gendoc; + } + + /** + * Lets the native PHP5 soap implementation handle the request + * after registrating the class + * @return void + */ + private function handleRequest(){ + //check if it's a legal webservice class + if(!in_array($this->name, $this->classNameArr)) + throw new Exception("No valid webservice class."); + + //check cache + //if(!file_exists($this->wsdlfile)) + $this->createWSDL(); + + $options = Array('actor' => $this->actor, 'classmap' => $this->structureMap); + + header("Content-type: text/xml"); + $this->server = new SoapServer($this->wsdlfile, $options); + $this->server->setClass($this->name); + $this->server->setPersistence($this->persistence); + + use_soap_error_handler(true); + $this->server->handle(); + } + + /** + * @param string code + * @param string string + * @param string actor + * @param mixed details + * @param string name + * @return void + */ + public function fault($code, $string, $actor, $details, $name='') { + return $this->server->fault($code, $string, $actor, $details, $name); + } + + /** + * Generates the documentations for the webservice usage. + * @TODO: "int", "boolean", "double", "float", "string", "void" + * @param string Template filename + * @return void + */ + public function createDocumentation($template="classes/soap/templates/docclass.xsl") { + if(!is_file($template)) + throw new WSException("Could not find the template file: '$template'"); + $this->class = new IPReflectionClass($this->name); + $xtpl = new IPXSLTemplate($template); + $documentation = Array(); + $documentation['menu'] = Array(); + //loop menu items + sort($this->classNameArr); + foreach($this->classNameArr as $className) { + $documentation['menu'][] = new IPReflectionClass($className); + } + + if($this->class){ + $this->class->properties = $this->class->getProperties(false, false); + $this->class->methods = $this->class->getMethods(false, false); + foreach((array)$this->class->methods as $method) { + $method->params = $method->getParameters(); + } + + $documentation['class'] = $this->class; + } + echo $xtpl->execute($documentation); + } +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/common.php b/webservice/classes/soap/common.php new file mode 100755 index 0000000..84324cc --- /dev/null +++ b/webservice/classes/soap/common.php @@ -0,0 +1,87 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ + + +error_reporting(E_ALL); +ob_start("ob_gzhandler"); + +require_once ("config.php"); + +if(!extension_loaded("soap")) + die("Soap extension not loaded!"); + +session_start(); + +/** autoload function for PHP5 +* Loads all the classes in the model +* +*/ +function __autoload($classname) { + try{ + if(file_exists("classes/soap/model/$classname.class.php")) + include("classes/soap/model/$classname.class.php"); + elseif(file_exists("classes/soap/$classname.class.php")) + include("classes/soap/$classname.class.php"); + elseif(file_exists("classes/soap/$classname.class.php")) + include("classes/soap/$classname.class.php"); + } catch (Exception $e) { + echo $e->getMessage(); + } + +} + +/** Write out debug file */ +function debug($txt,$file="debug.txt"){ + $fp = fopen($file, "a"); + fwrite($fp, str_replace("\n","\r\n","\r\n".$txt)); + fclose($fp); +} + +/** Write out object in the debug log */ +function debugObject($txt,$obj){ + ob_start(); + print_r($obj); + $data = ob_get_contents(); + ob_end_clean(); + debug($txt."\n".$data); +} +?> diff --git a/webservice/classes/soap/config.php b/webservice/classes/soap/config.php new file mode 100755 index 0000000..6344a5a --- /dev/null +++ b/webservice/classes/soap/config.php @@ -0,0 +1,23 @@ + "contact", + "address" => "address", + +); + +?> \ No newline at end of file diff --git a/webservice/classes/soap/model/RestService.class.php b/webservice/classes/soap/model/RestService.class.php new file mode 100755 index 0000000..2f507bf --- /dev/null +++ b/webservice/classes/soap/model/RestService.class.php @@ -0,0 +1,155 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ + + +/** + * Class Service - will act as a switch between + * SOAP requests and REST requests + *@author KnowledgeTree Team + *@package Webservice + *@version Version 0.9 + */ +class RestService +{ + /** + * Class construct + * + * @param object_type $object + * @return jason encoded string + */ + public function __construct($object) + { + try { + + $result = $this->runAsService($object); + echo jason_encode(array('status_code' => 0,'result' => $result)); + + } catch (Exception $e) { + echo json_encode(array('status_code' => 1,'result' => $e->getMessage())); + } + } + + /** + * Constructor for invoking a reflection object + * Initiates the object as a service + * @param class $object + * @access private + * @return class instance + */ + + private function runAsService($object) + { + + if (!isset($_GET['class'])) { + + throw new Exception('Method name not specified.'); + } + + $reflObject = new ReflectionObject($object); + + if (!$reflObject->hasMethod($_GET['class'])) { + + throw new Exception('There is no method with this name.'); + + } + + $reflMethod = $reflObject->getMethod($_GET['class']); + + if ( !$reflMethod->isPublic() || $reflMethod->isStatic() || $reflMethod->isInternal() ) { + + throw new Exception('Invalid method name specified.'); + + } + + $reflParameters = $reflMethod->getParameters(); + + $args = array(); + + + foreach ($reflParameters as $param) { + + $paramName = $param->getName(); + + if (!isset($_GET[$paramName])) { + + if ($param->isDefaultValueAvailable()) { + + $paramValue = $param->getDefaultValue(); + + } else { + + throw new Exception('Required parameter "'.$paramName.'" is not specified.'); + + } + + } else { + + $paramValue = $_GET[$paramName]; + + } + + + if ($param->getClass()) { + + throw new Exception('The method contains unsupported parameter type: class object.'); + + } + + + if ($param->isArray() && !is_array($paramValue)) { + + throw new Exception('Array expected for parameter "'.$paramName.'", but scalar found.'); + + } + + + $args[$param->getPosition()] = $paramValue; + + } + + return $reflMethod->invokeArgs($object, $args); + + } + +} + +?> \ No newline at end of file diff --git a/webservice/classes/soap/model/address.class.php b/webservice/classes/soap/model/address.class.php new file mode 100755 index 0000000..116069d --- /dev/null +++ b/webservice/classes/soap/model/address.class.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/webservice/classes/soap/model/contact.class.php b/webservice/classes/soap/model/contact.class.php new file mode 100755 index 0000000..74e0ae8 --- /dev/null +++ b/webservice/classes/soap/model/contact.class.php @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/webservice/classes/soap/model/contactManager.class.php b/webservice/classes/soap/model/contactManager.class.php new file mode 100755 index 0000000..a192add --- /dev/null +++ b/webservice/classes/soap/model/contactManager.class.php @@ -0,0 +1,58 @@ +address = new Address(); + $contact->address->city ="sesamcity"; + $contact->address->street ="sesamstreet"; + $contact->email = "me@you.com"; + $contact->id = 1; + $contact->name ="me"; + + $ret[] = $contact; + return $ret; + } + + /** + * Gets the contact with the given id. + * @param int The id + * @return contact + */ + public function getContact($id) { + //get contact from db + //might wanna throw an exception when it does not exists + throw new Exception("Contact '$id' not found"); + } + /** + * Generates an new, empty contact template + * @return contact + */ + public function newContact() { + return new contact(); + } + + /** + * Saves a given contact + * @param contact + * @return void + */ + public function saveContact(contact $contact) { + $contact->save(); + } + +} +?> \ No newline at end of file diff --git a/webservice/classes/soap/templates/css/doc.css b/webservice/classes/soap/templates/css/doc.css new file mode 100755 index 0000000..1c92f1f --- /dev/null +++ b/webservice/classes/soap/templates/css/doc.css @@ -0,0 +1,88 @@ +body { + margin:0; + padding:0; + text-align: center; + background: #ffffff url("../images/doc/back.gif") repeat-y center; +} +body, table, th, td, p, div { + font-family: Arial, Helvetica; + font-size: 12pt; + line-height:1.4; +} +#main { + margin-right: auto; + margin-left: auto; + text-align: left; + width: 980px; +} +#mainpadded { + padding: 0px 40px 80px 40px; +} +#mainheader { + background: #ffffff url("../images/doc/backtop.jpg") no-repeat; + height: 180px; +} +#mainheaderpadded { + padding: 130px 40px 0px 40px; + text-align: right; +} +td { + vertical-align: top; +} +td#menu { + width: 300px; +} +td#content { +} +div.method { + padding-left: 10px; + margin-bottom: 20px; + border-left: 2px solid #C0C0C0; +} +div.methodwarning { + padding-left: 10px; + margin-bottom: 20px; + border-left: 4px solid #FF0000; +} +div.property { + padding-left: 10px; + margin-bottom: 20px; + border-left: 2px solid #C0C0C0; +} +div.propertywarning { + padding-left: 10px; + margin-bottom: 20px; + border-left: 4px solid #FF0000; +} +div.warning { + font-weight: bold; + color: #FF0000; + float: left; +} +a { + color: #FFA619; + text-decoration: none; + font-weight: bold; +} +a:hover { + text-decoration: underline; +} +p { + margin-top: 5px; + margin-bottom: 10px; +} +h1 { + margin-top: 0px; + margin-bottom: 10px; + padding: 0px; + font-family: Verdana, Helvetica; + font-size: 20pt; + color: #000000; +} +h2 { + margin-top: 0px; + margin-bottom: 10px; + font-family: Verdana, Helvetica; + font-size: 16pt; + color: #000000; +} \ No newline at end of file diff --git a/webservice/classes/soap/templates/docclass.xsl b/webservice/classes/soap/templates/docclass.xsl new file mode 100755 index 0000000..380bdc4 --- /dev/null +++ b/webservice/classes/soap/templates/docclass.xsl @@ -0,0 +1,116 @@ + + + + + + + + Webservices + + + +
+
+
+ +

 [WSDL]

+
+
+
+
+ + + + + +
+ + + + + +

Full description

+

+ +

Properties

+ + +
+
+ + + + + type
+
+ + type
+
+
+
+ +
missing type info

+
+
+ +
+
+ +

Methods

+ + +
+ ( + + + , + + ) +
+ + + + + returns
+
+ + returns
+
+
+
+ +
missing return value

+
+
+ + + throws
+
+
+
+
+
+
+
+ +
+
+
+ + +
+
\ No newline at end of file diff --git a/webservice/classes/soap/templates/images/doc/back.gif b/webservice/classes/soap/templates/images/doc/back.gif new file mode 100755 index 0000000..202a0f4 --- /dev/null +++ b/webservice/classes/soap/templates/images/doc/back.gif diff --git a/webservice/classes/soap/templates/images/doc/backbottom.jpg b/webservice/classes/soap/templates/images/doc/backbottom.jpg new file mode 100755 index 0000000..cc6cb62 --- /dev/null +++ b/webservice/classes/soap/templates/images/doc/backbottom.jpg diff --git a/webservice/classes/soap/templates/images/doc/backtop.jpg b/webservice/classes/soap/templates/images/doc/backtop.jpg new file mode 100755 index 0000000..5fe6b1f --- /dev/null +++ b/webservice/classes/soap/templates/images/doc/backtop.jpg diff --git a/webservice/classes/soap/templates/images/doc/warning.gif b/webservice/classes/soap/templates/images/doc/warning.gif new file mode 100755 index 0000000..efbfa44 --- /dev/null +++ b/webservice/classes/soap/templates/images/doc/warning.gif diff --git a/webservice/classes/soap/templates/str.replace.function.xsl b/webservice/classes/soap/templates/str.replace.function.xsl new file mode 100755 index 0000000..5d74e86 --- /dev/null +++ b/webservice/classes/soap/templates/str.replace.function.xsl @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ERROR: function implementation of str:replace() relies on exsl:node-set(). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webservice/documentation.php b/webservice/documentation.php new file mode 100755 index 0000000..422f6de --- /dev/null +++ b/webservice/documentation.php @@ -0,0 +1,57 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ +//need to manually include for the function 'get_declared_classes()' +include_once("classes/soap/IPPhpDoc.class.php"); +include_once("classes/soap/IPReflectionClass.class.php"); +include_once("classes/soap/IPReflectionCommentParser.class.php"); +include_once("classes/soap/IPReflectionMethod.class.php"); +include_once("classes/soap/IPReflectionProperty.class.php"); +include_once("classes/soap/IPXMLSchema.class.php"); +include_once("classes/soap/WSDLStruct.class.php"); +include_once("classes/soap/WSHelper.class.php"); +include_once("classes/soap/IPXSLTemplate.class.php"); + +$phpdoc=new IPPhpdoc(); +if(isset($_GET['class'])) $phpdoc->setClass($_GET['class']); +echo $phpdoc->getDocumentation(); +?> \ No newline at end of file diff --git a/webservice/restservice.php b/webservice/restservice.php new file mode 100755 index 0000000..37d1f01 --- /dev/null +++ b/webservice/restservice.php @@ -0,0 +1,70 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ + +ob_start("ob_gzhandler"); +session_start(); + +if(file_exists("classes/rest/Server.php")) + include("classes/rest/Server.php"); +if (file_exists("classes/rest/model/contactManager.class.php")) + include("classes/rest/model/contactManager.class.php"); +if (file_exists("classes/rest/model/RestService.class.php")) + include("classes/rest/model/RestService.class.php"); + + +try { + $server = new Rest_Server(); + + $server->setClass("contactManager"); + + //$server->addFunction('getContacts'); + + $server->handle(); + //var_dump($server); + + + }catch(Exception $e) { + //possible db transaction rollback + $server->fault("SERVER", $e->getMessage(),"", $e->__toString()); + } +exit; +?> \ No newline at end of file diff --git a/webservice/tests/RESTClient.php b/webservice/tests/RESTClient.php new file mode 100644 index 0000000..d8e8610 --- /dev/null +++ b/webservice/tests/RESTClient.php @@ -0,0 +1,76 @@ +root_url = $this->curr_url = $root_url; + $this->user_name = $user_name; + $this->password = $password; + if ($root_url != "") { + $this->createRequest("GET"); + $this->sendRequest(); + } + return true; + } + + public function createRequest($url, $method, $arr = null) { + $this->curr_url = $url; + $this->req =& new HTTP_Request($url); + if ($this->user_name != "" && $this->password != "") { + $this->req->setBasicAuth($this->user_name, $this->password); + } + + switch($method) { + case "GET": + $this->req->setMethod(HTTP_REQUEST_METHOD_GET); + break; + case "POST": + $this->req->setMethod(HTTP_REQUEST_METHOD_POST); + $this->addPostData($arr); + break; + case "PUT": + $this->req->setMethod(HTTP_REQUEST_METHOD_PUT); + // to-do + break; + case "DELETE": + $this->req->setMethod(HTTP_REQUEST_METHOD_DELETE); + // to-do + break; + } + } + + private function addPostData($arr) { + if ($arr != null) { + foreach ($arr as $key => $value) { + $this->req->addPostData($key, $value); + } + } + } + + public function sendRequest() { + $this->response = $this->req->sendRequest(); + + if (PEAR::isError($this->response)) { + echo $this->response->getMessage(); + die(); + } else { + $this->responseBody = $this->req->getResponseBody(); + } + } + + public function getResponse() { + return $this->responseBody; + } + +} +?> \ No newline at end of file diff --git a/webservice/tests/Rest.php b/webservice/tests/Rest.php new file mode 100644 index 0000000..3293edd --- /dev/null +++ b/webservice/tests/Rest.php @@ -0,0 +1,19 @@ +createRequest("$url","GET",''); +$rest->sendRequest(); +$output = $rest->getResponse(); +echo $output; + +?> \ No newline at end of file diff --git a/webservice/tests/annotations.php b/webservice/tests/annotations.php new file mode 100755 index 0000000..e10ef44 --- /dev/null +++ b/webservice/tests/annotations.php @@ -0,0 +1,44 @@ +'you'); + */ +class something{ + /** + * @var string + * @Controller(type => DefaultController::TYPE_PLAIN, length => 100) + */ + public $propertyA; + + /** + * @var string + * @Controller(type => DefaultController::TYPE_HTML, length => 100) + */ + public function methodB () { + return "aap"; + } +} + +/* Annotation example */ +$rel = new IPReflectionClass("something"); +$properties = $rel->getProperties(); +$methods = $rel->getMethods(); + +var_dump($rel->getAnnotation("ann1", "stdClass")); + +$property = $properties["propertyA"]; +$ann = $property->getAnnotation("Controller", "DefaultController"); +var_dump($ann); + +$method = $methods["methodB"]; +$ann = $method->getAnnotation("Controller", "DefaultController"); +var_dump($ann); +?> \ No newline at end of file diff --git a/webservice/tests/webservice.php b/webservice/tests/webservice.php new file mode 100755 index 0000000..e0cefbe --- /dev/null +++ b/webservice/tests/webservice.php @@ -0,0 +1,17 @@ +WSDL file: ".$wsdl."
\n"; + +$options = Array('actor' =>'http://www.knowledgetree.pr', + 'trace' => true); +$client = new SoapClient($wsdl,$options); + +echo "
Result from getContacts call:
"; + +$res = $client->getContacts(); +print_r($res); +echo "
Raw Soap response:
"; +echo htmlentities($client->__getLastResponse()); +echo "
SoapFault asking for an unknown contact:
"; +$client->newContact(); +?> \ No newline at end of file diff --git a/webservice/webservice.php b/webservice/webservice.php new file mode 100755 index 0000000..dcf0195 --- /dev/null +++ b/webservice/webservice.php @@ -0,0 +1,66 @@ +. +* +* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, +* California 94120-7775, or email info@knowledgetree.com. +* +* The interactive user interfaces in modified source and object code versions +* of this program must display Appropriate Legal Notices, as required under +* Section 5 of the GNU General Public License version 3. +* +* In accordance with Section 7(b) of the GNU General Public License version 3, +* these Appropriate Legal Notices must retain the display of the "Powered by +* KnowledgeTree" logo and retain the original copyright notice. If the display of the +* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices +* must display the words "Powered by KnowledgeTree" and retain the original +* copyright notice. +* +* @copyright 2008-2009, KnowledgeTree Inc. +* @license GNU General Public License version 3 +* @author KnowledgeTree Team +* @package Webservice +* @version Version 0.1 +*/ + +require_once "classes/soap/common.php"; + +if($_GET['class'] && (in_array($_GET['class'], $WSClasses) || in_array($_GET['class'], $WSStructures))) { + $WSHelper = new WSHelper("http://www.knowledgetree.com", $_GET['class']); + $WSHelper->actor = "http://www.knowledgetree.com"; + $WSHelper->use = SOAP_ENCODED; + $WSHelper->classNameArr = $WSClasses; + $WSHelper->structureMap = $WSStructures; + $WSHelper->setPersistence(SOAP_PERSISTENCE_REQUEST); + $WSHelper->setWSDLCacheFolder('wsdl/'); //trailing slash mandatory. Default is 'wsdl/' + + try { + + $WSHelper->handle(); + //possible db transaction commit + }catch(Exception $e) { + //possible db transaction rollback + $WSHelper->fault("SERVER", $e->getMessage(),"", $e->__toString()); + } +} else { + die("No valid class selected"); +} + +?> \ No newline at end of file diff --git a/webservice/wsdl/contactManager.wsdl b/webservice/wsdl/contactManager.wsdl new file mode 100644 index 0000000..d7ba892 --- /dev/null +++ b/webservice/wsdl/contactManager.wsdl @@ -0,0 +1,2 @@ + +