0011760: create smd from model definition
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 21 Apr 2016 15:17:29 +0000 (17:17 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 23 Jun 2016 13:26:55 +0000 (15:26 +0200)
* dynamically adds methods/service definition to
 json server
* use request content in json server handle()
  instead of php://input
* adds cache lazy loading

https://forge.tine20.org/view.php?id=11760

Change-Id: Ibcae79e82b23fa42ec48cca84d51063ec832f221
Reviewed-on: http://gerrit.tine20.com/customers/3073
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tinebase/Server/JsonTests.php
tine20/Inventory/Frontend/Json.php
tine20/Tinebase/Controller/Abstract.php
tine20/Tinebase/Core.php
tine20/Tinebase/Server/Json.php

index 166e8fc..94ae428 100644 (file)
@@ -44,7 +44,6 @@ class Tinebase_Server_JsonTests extends TestCase
                 array
                 (
                     'type' => 'array',
-                    'name' => 'recordData',
                     'optional' => false,
                 )
 
@@ -60,12 +59,11 @@ class Tinebase_Server_JsonTests extends TestCase
                 array
                 (
                     'type' => 'array',
-                    'name' => 'ids',
-                    'optional' => '',
+                    'optional' => false,
                 )
 
             ),
-            'returns' => 'string'
+            'returns' => 'array'
         ), $smdArray['services']['Inventory.deleteInventoryItems']);
     }
 
@@ -81,4 +79,47 @@ class Tinebase_Server_JsonTests extends TestCase
         $smdArray = $smd->toArray();
         $this->assertTrue(isset($smdArray['services']['Tinebase.ping']));
     }
+
+    /**
+     * @group ServerTests
+     *
+     * @see  0011760: create smd from model definition
+     */
+    public function testHandleRequestForDynamicAPI()
+    {
+        // handle jsonkey check
+        $jsonkey = 'myawsomejsonkey';
+        $_SERVER['HTTP_X_TINE20_JSONKEY'] = $jsonkey;
+        $coreSession = Tinebase_Session::getSessionNamespace();
+        $coreSession->jsonKey = $jsonkey;
+
+        $server = new Tinebase_Server_Json();
+        $request = \Zend\Http\PhpEnvironment\Request::fromString(<<<EOS
+POST /index.php?requestType=JSON HTTP/1.1\r
+Host: localhost\r
+User-Agent: Mozilla/5.0 (X11; Linux i686; rv:15.0) Gecko/20120824 Thunderbird/15.0 Lightning/1.7\r
+Content-Type: application/json\r
+X-Tine20-Transactionid: 18da265bc0eb66a36081bfd42689c1675ed68bab\r
+X-Requested-With: XMLHttpRequest\r
+Accept: */*\r
+Referer: http://tine20.vagrant/\r
+Accept-Encoding: gzip, deflate\r
+Accept-Language: en-US,en;q=0.8,de-DE;q=0.6,de;q=0.4\r
+\r
+{"jsonrpc":"2.0","method":"Inventory.searchInventoryItems","params":{"filter":[], "paging":{}},"id":6}
+EOS
+        );
+        ob_start();
+        $server->handle($request);
+        $out = ob_get_clean();
+        //echo $out;
+
+        $this->assertTrue(! empty($out), 'request should not be empty');
+        $this->assertNotContains('Not Authorised', $out);
+        $this->assertNotContains('Method not found', $out);
+        $this->assertNotContains('No Application Controller found', $out);
+        $this->assertNotContains('"error"', $out);
+        $this->assertNotContains('PHP Fatal error', $out);
+        $this->assertContains('"result"', $out);
+    }
 }
index ddcc3de..bf889e9 100644 (file)
  */
 class Inventory_Frontend_Json extends Tinebase_Frontend_Json_Abstract
 {
-    /**
-     * the controller
-     *
-     * @var Inventory_Controller_InventoryItem
-     */
-    protected $_controller = NULL;
+    protected $_applicationName = 'Inventory';
     
     /**
      * the models handled by this frontend
@@ -41,61 +36,6 @@ class Inventory_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     );
 
     /**
-     * the constructor
-     *
-     */
-    public function __construct()
-    {
-        $this->_applicationName = 'Inventory';
-        $this->_controller = Inventory_Controller_InventoryItem::getInstance();
-    }
-
-    /**
-     * Search for records matching given arguments
-     *
-     * @param  array $filter
-     * @param  array $paging
-     * @return array
-     */
-    public function searchInventoryItems($filter, $paging)
-    {
-        return $this->_search($filter, $paging, $this->_controller, 'Inventory_Model_InventoryItemFilter', TRUE);
-    }
-
-    /**
-     * Return a single record
-     *
-     * @param   string $id
-     * @return  array record data
-     */
-    public function getInventoryItem($id)
-    {
-        return $this->_get($id, $this->_controller);
-    }
-
-    /**
-     * creates/updates a record
-     *
-     * @param  array $recordData
-     * @return array created/updated record
-     */
-    public function saveInventoryItem($recordData)
-    {
-        return $this->_save($recordData, $this->_controller, 'InventoryItem');
-    }
-
-    /**
-     * deletes existing records
-     *
-     * @param  array  $ids
-     * @return string
-     */
-    public function deleteInventoryItems($ids)
-    {
-        return $this->_delete($ids, $this->_controller);
-    }
-
-    /**
      * get inventory import definitions
      *
      * @return Tinebase_Record_RecordSet
index 8e25c13..cee387a 100755 (executable)
@@ -263,7 +263,7 @@ abstract class Tinebase_Controller_Abstract extends Tinebase_Pluggable_Abstract
      */
     public function getModels()
     {
-        if ($this->_models === null) {
+        if ($this->_models === null && ! empty($this->_applicationName)) {
             try {
                 $dir = new DirectoryIterator(dirname(dirname(dirname(__FILE__))) . '/' . $this->_applicationName . '/Model/');
             } catch (Exception $e) {
index 4717fa2..0003915 100644 (file)
@@ -450,10 +450,11 @@ class Tinebase_Core
      */
     public static function startCoreSession()
     {
-        Tinebase_Session::setSessionBackend();
-        
-        Zend_Session::start();
-        
+        if (! Tinebase_Session::isStarted()) {
+            Tinebase_Session::setSessionBackend();
+            Zend_Session::start();
+        }
+
         $coreSession = Tinebase_Session::getSessionNamespace();
         
         if (isset($coreSession->currentAccount)) {
@@ -497,11 +498,16 @@ class Tinebase_Core
      */
     public static function setupBuildConstants()
     {
+        if (defined('TINE20_BUILDTYPE')) {
+            // only define constants once
+            return;
+        }
         $config = self::getConfig();
-        define('TINE20_BUILDTYPE',     strtoupper($config->get('buildtype', 'DEVELOPMENT')));
-        define('TINE20_CODENAME',      Tinebase_Helper::getDevelopmentRevision());
+
+        define('TINE20_BUILDTYPE', strtoupper($config->get('buildtype', 'DEVELOPMENT')));
+        define('TINE20_CODENAME', Tinebase_Helper::getDevelopmentRevision());
         define('TINE20_PACKAGESTRING', 'none');
-        define('TINE20_RELEASETIME',   'none');
+        define('TINE20_RELEASETIME', 'none');
     }
     
     /**
@@ -811,8 +817,9 @@ class Tinebase_Core
                 . " Filesdir config value not set. tine20:// streamwrapper not registered, virtual filesystem not available.");
             return;
         }
-        
-        stream_wrapper_register('tine20', 'Tinebase_FileSystem_StreamWrapper');
+        if (! in_array('tine20', stream_get_wrappers())) {
+            stream_wrapper_register('tine20', 'Tinebase_FileSystem_StreamWrapper');
+        }
     }
     
     /**
@@ -1338,6 +1345,10 @@ class Tinebase_Core
      */
     public static function getCache()
     {
+        if (! self::get(self::CACHE) instanceof Zend_Cache_Core) {
+            self::$cacheStatus = null;
+            Tinebase_Core::setupCache();
+        }
         return self::get(self::CACHE);
     }
     
index 32712ac..1e31bbb 100644 (file)
@@ -41,6 +41,9 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
         $this->_body    = $body !== null ? $body : fopen('php://input', 'r');
         
         $request = $request instanceof \Zend\Http\Request ? $request : new \Zend\Http\PhpEnvironment\Request();
+
+        // only for debugging
+        //Tinebase_Core::getLogger()->DEBUG(__METHOD__ . '::' . __LINE__ . " raw request: " . $request->__toString());
         
         // handle CORS requests
         if ($request->getHeaders()->has('ORIGIN') && !$request->getHeaders()->has('X-FORWARDED-HOST')) {
@@ -133,11 +136,12 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
             }
         }
         
-        $json = file_get_contents('php://input');
+        $json = $request->getContent();
         $json = Tinebase_Core::filterInputForDatabase($json);
-        
+
         if (substr($json, 0, 1) == '[') {
-            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' batched request');
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                . ' batched request');
             $isBatchedRequest = true;
             $requests = Zend_Json::decode($json);
         } else {
@@ -152,7 +156,8 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
                     $_requests[0]["params"][$field] = "*******";
                 }
             }
-            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' is JSON request. rawdata: ' . print_r($_requests, true));
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                . ' is JSON request. rawdata: ' . print_r($_requests, true));
         } 
         
         $response = array();
@@ -165,12 +170,15 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
                    $this->_handleException($request, $exception) :
                    $this->_handle($request);
             } else {
-                if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Got empty request options: skip request.');
+                if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
+                    . ' Got empty request options: skip request.');
                 $response[] = NULL;
             }
         }
-        
-        header('Content-type: application/json');
+
+        if (! headers_sent()) {
+            header('Content-type: application/json');
+        }
         echo $isBatchedRequest ? '['. implode(',', $response) .']' : $response[0];
     }
     
@@ -229,13 +237,24 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
                 }
             }
         }
+
+        if (Tinebase_Core::isRegistered(Tinebase_Core::USER)) {
+            // TODO pass classes here??
+            self::_addModelConfigMethods($server);
+        }
         
         if (isset($cache)) {
             $cache->save($server, $cacheId, array(), null);
         }
-        
+
         return $server;
     }
+
+    protected static function _addModelConfigMethods(Zend_Json_Server $server)
+    {
+        $definitions = self::_getModelConfigMethods();
+        $server->loadFunctions($definitions);
+    }
     
     /**
      * handler for JSON api requests
@@ -289,7 +308,7 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
             }
             
             $server = self::_getServer($classes);
-            
+
             $response = $server->handle($request);
             if ($response->isError()) {
                 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Got response error: '
@@ -373,105 +392,84 @@ class Tinebase_Server_Json extends Tinebase_Server_Abstract implements Tinebase_
             
         $smd = $server->getServiceMap();
 
-        if (Tinebase_Core::isRegistered(Tinebase_Core::USER)) {
-            // TODO make it work
-            //self::_addModelConfigServices($smd);
-        }
-
         return $smd;
     }
 
     /**
-     * add default modelconfig functions to service map
+     * get default modelconfig methods
      *
-     * @param Zend_Json_Server_Smd $smd
+     * @return array of Zend_Server_Method_Definition
      */
-    protected static function _addModelConfigServices(Zend_Json_Server_Smd $smd)
+    protected static function _getModelConfigMethods()
     {
         $userApplications = Tinebase_Core::getUser()->getApplications(/* $_anyRight */ TRUE);
 
-        $cache = Tinebase_Core::getCache();
-        $cacheId = '_addModelConfigServices' . sha1(Zend_Json_Encoder::encode($userApplications->getArrayOfIds()));
-        $services = $cache->load($cacheId);
-
-        if (! $services) {
-            $services = array();
-            foreach ($userApplications as $application) {
-                try {
-                    $controller = Tinebase_Core::getApplicationInstance($application->name);
-                    $models = $controller->getModels();
-                    if (!$models) {
-                        continue;
-                    }
-                } catch (Tinebase_Exception_NotFound $tenf) {
+        $definitions = array();
+        foreach ($userApplications as $application) {
+            try {
+                $controller = Tinebase_Core::getApplicationInstance($application->name);
+                $models = $controller->getModels();
+                if (!$models) {
                     continue;
                 }
+            } catch (Tinebase_Exception_NotFound $tenf) {
+                continue;
+            }
 
-                foreach ($models as $model) {
-                    $config = function_exists($model . '::getConfiguration') ? $model::getConfiguration() : null;
-                    if ($config && $config->exposeJsonApi) {
-                        $commonServiceOptions = array(
-                            'envelope' => 'JSON-RPC-2.0',
-                            'transport' => 'POST',
-                            'return' => 'array'
-                        );
+            foreach ($models as $model) {
+                $config = $model::getConfiguration();
+                if ($config && $config->exposeJsonApi) {
+                    // TODO replace this with generic method
+                    $simpleModelName = str_replace($application->name . '_Model_', '', $model);
 
-                        // TODO replace this with generic method
-                        $simpleModelName = str_replace($application->name . '_Model_', '', $model);
+                    $commonJsonApiMethods = array(
+                        'get' => array(
+                            'params' => array('string'),
+                            'help'   => 'get one ' . $simpleModelName . ' identified by $id',
+                            'plural' => false,
+                        ),
+                        'search' => array(
+                            'params' => array('array', 'array'),
+                            'help'   => 'Search for ' . $simpleModelName . 's matching given arguments',
+                            'plural' => true,
+                        ),
+                        'save' => array(
+                            'params' => array('array'),
+                            'help'   => 'Save ' . $simpleModelName . '',
+                            'plural' => false,
+                        ),
+                        'delete' => array(
+                            'params' => array('array'),
+                            'help'   => 'Delete multiple ' . $simpleModelName . 's',
+                            'plural' => true,
+                        ),
+                    );
 
-                        $services[] = array_merge($commonServiceOptions, array(
-                            'name' => $application->name . '.get' . $simpleModelName,
-                            'params' =>
-                                array(array(
-                                    'type' => 'string',
-                                    'name' => 'id',
-                                    'optional' => false,
-                                )),
-                        ));
-                        $services[] = array_merge($commonServiceOptions, array(
-                            'name' => $application->name . '.save' . $simpleModelName,
-                            'params' =>
-                                array(array(
-                                    'type' => 'any',
-                                    'name' => 'recordData',
-                                    'optional' => false,
-                                )),
-                        ));
-                        $services[] = array_merge($commonServiceOptions, array(
-                            'name' => $application->name . '.delete' . $simpleModelName . 's',
-                            'params' =>
-                                array(array(
-                                    'type' => 'array',
-                                    'name' => 'ids',
-                                    'optional' => false,
-                                )),
-                            'return' => 'string'
-                        ));
-                        $services[] = array_merge($commonServiceOptions, array(
-                            'name' => $application->name . '.search' . $simpleModelName . 's',
-                            'params' =>
-                                array(array(
-                                    'type' => 'array',
-                                    'name' => 'filter',
-                                    'optional' => false,
-                                ), array(
-                                    'type' => 'array',
-                                    'name' => 'paging',
-                                    'optional' => false,
-                                )),
+                    foreach ($commonJsonApiMethods as $name => $method) {
+                        $key = $application->name . '.' . $name . $simpleModelName . ($method['plural'] ? 's' : '');
+                        $definitions[$key] = new Zend_Server_Method_Definition(array(
+                            'name'            => $key,
+                            'prototypes'      => array(array(
+                                'returnType' => 'array',
+                                'parameters' => $method['params']
+                            )),
+                            'methodHelp'      => $method['help'],
+                            'invokeArguments' => array(),
+                            'object'          => null,
+                            'callback'        => array(
+                                'type'   => 'instance',
+                                'class'  => $application->name . '_Frontend_Json',
+                                'method' => $name . $simpleModelName . ($method['plural'] ? 's' : '')
+                            ),
                         ));
                     }
                 }
             }
-
-            $cache->save($services, $cacheId);
         }
 
-        foreach ($services as $service) {
-            $smd->addService($service);
-        }
+        return $definitions;
     }
-    
+
     /**
      * check json key
      *