Restful Feature, improvements on rest-gen cli command and Disptacher

On Dispatcher:
- Now it is handling Cross Domain AJAX request, it is accepting requests with method OPTIONS
- Now the behaviour of rest server is modified by confuguration
- The dispatcher can be load configuraion from a processmaker core dir. for all workspaces
and for a determinated workspace

On Cli Command:
- Now it can generate api crud for processmaker root dir and a determinated workspace
- More improvements to handle correctly build request for a plugin or a workspace or core of pmos
This commit is contained in:
Erik Amaru Ortiz
2012-08-30 16:26:19 -04:00
parent 6e98245f3f
commit de62be5506
6 changed files with 539 additions and 357 deletions

View File

@@ -2,54 +2,123 @@
/**
* Class Service_Rest_RestTool
*
* This tool generate a rest-config.ini file and build rest crud api for 'Restler' lib.
* This tool generates a rest-config.ini file and build rest crud api for 'Restler' lib.
* Class since: Aug 22, 2012
*
* @author Erik Amaru Ortiz <aortiz.erik@gmail.com>
*/
class Service_Rest_RestTool
{
/**
* Stores absolute file path of rest configuration ini file (rest-config.ini)
* @var string
*/
protected $configFile = '';
/**
* Stores configuration loaded from configuration ini file
* @var array
*/
protected $config = array();
/**
* Stores absolute filename patgh of database xml schema
* @var string
*/
protected $dbXmlSchemaFile = '';
/**
* Stores information of pmos tables
* @var array
*/
protected $dbInfo = array();
/**
* Stores obsolute base path to output generated files
* @var string
*/
protected $basePath = '';
public function __construct()
/**
* Init method to initialize the class after previous configurations
*/
public function init()
{
$this->basePath = PATH_CORE;
$this->dbXmlSchemaFile = 'config/schema.xml';
$this->configFile = 'config/rest-config.ini';
self::out('ProcessMaker Rest Crud Api Generator Tool v1.0', 'success', true);
echo PHP_EOL;
// configuring paths by default if there were not configured before.
if (empty($this->dbXmlSchemaFile)) {
$this->dbXmlSchemaFile = $this->basePath . 'config/schema.xml';
}
if (empty($this->configFile)) {
$this->configFile = $this->basePath . 'config/rest-config.ini';
}
}
public function setBasePath($path)
public function setConfigFile($configFile)
{
$this->basePath = $path;
$this->configFile = $configFile;
}
public function setDbXmlSchemaFile($dbXmlSchemaFile)
{
$this->dbXmlSchemaFile = $dbXmlSchemaFile;
}
public function setBasePath($basePath)
{
$this->basePath = $basePath;
}
/**
* Load rest ini. configuration
*/
protected function loadConfig()
{
$configFile = $this->basePath . $this->configFile;
if (! file_exists($configFile)) {
throw new Exception(sprintf("Runtime Error: Configuration file '%s' doesn't exist!", $configFile));
if (! file_exists($this->configFile)) {
throw new Exception(sprintf("Runtime Error: Configuration file '%s' doesn't exist!", $this->configFile));
}
$this->config = @parse_ini_file($configFile, true);
self::out('Loading config from: ', 'info', false);
echo $this->configFile . "\n";
$config = @parse_ini_file($this->configFile, true);
// parse composed sections names like [TABLE:SOME_TABLE]
foreach ($config as $key => $value) {
$sectionParts = explode(':', $key);
if (count($sectionParts) == 2 && strtolower($sectionParts[0]) == 'table') {
$this->config['_tables'][$sectionParts[1]] = $value;
} else {
$this->config[$key] = $value;
}
}
}
/**
* Load db schema from xml file
*/
protected function loadDbXmlSchema()
{
$dbXmlSchemaFile = $this->basePath . $this->dbXmlSchemaFile;
if (! file_exists($dbXmlSchemaFile)) {
throw new Exception(sprintf("Runtime Error: Configuration file '%s' doesn't exist!", $dbXmlSchemaFile));
if (! file_exists($this->dbXmlSchemaFile)) {
throw new Exception(sprintf(
"Runtime Error: Configuration file '%s' doesn't exist!", $this->dbXmlSchemaFile
));
}
self::out('Loading Xml Schema from: ', 'info', false);
echo $this->dbXmlSchemaFile . "\n";
$doc = new Xml_DOMDocumentExtended();
$doc->load($dbXmlSchemaFile);
$doc->load($this->dbXmlSchemaFile);
// dump to array
$data = $doc->toArray();
$tables = $data['database']['table'];
// process just relevant information
foreach ($tables as $table) {
$this->dbInfo[$table['@name']]['pKeys'] = array();
$this->dbInfo[$table['@name']]['columns'] = array();
@@ -69,25 +138,73 @@ class Service_Rest_RestTool
}
}
/**
* Build Rest configuration file
* @param string $filename configutaion filename to be generated
*/
public function buildConfigIni($filename = '')
{
$this->loadDbXmlSchema();
$configFile = empty($filename) ? $this->basePath . $this->configFile : $filename;
$configIniStr = "; -= ProcessMaker RestFul services configuration =-\n";
$configIniStr .= "\n";
$configIniStr .= "; On this configuration file you can customize some aspects to expose on rest service.\n";
$configIniStr .= "; Configure what methods (GET,POST,PUT,DELETE) should be exposed by ProcessMaker Rest server.\n";
$configIniStr .= "; Configure for each table/method what columns sould be exposed.\n";
$configIniStr .= "\n";
$configIniStr = <<<EOT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; -= ProcessMaker RestFul services configuration =- ;
; ;
; On this configuration file you can customize the Processmaker Rest Service. ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Rest Service Configuration ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
enable_service = true
; add headers to rest server responses
[HEADERS]
; Enable this header to allow "Cross Domain AJAX" requests;
; This works because processmaker is handling correctly requests with method 'OPTIONS'
; that automatically is sent by a client using XmlHttpRequest or similar.
;Access-Control-Allow-Origin = *
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; DB Tables Crud generation Config ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This configuration section is used by ./rest-gen command to build the Rest Crud Api
;
; Configure what methods GET, POST, PUT, DELETE should be exposed.
; Configure for each table/method what columns sould be exposed.
;
; Configuration for each table must starts with a section like:
; [TABLE:<table-name>]
;
; inside of each section there are two config keys:
;
; "ALLOW_METHODS" -> Use this param to set allowed methods (separeted by a single space).
; Complete example:
; ALLOW_METHODS = GET POST PUT DELETE
;
; The others params are: "EXPOSE_COLUMNS_GET", "EXPOSE_COLUMNS_POST" and "EXPOSE_COLUMNS_PUT"
; this params are used to configure the columns that should be exposed;
; wildcard '*' can be used to speccify all columns.
;
; Example:
;
;[TABLE:MY_TABLE]
; ALLOW_METHODS = GET POST PUT DELETE
; EXPOSE_COLUMNS_GET = *
; EXPOSE_COLUMNS_POST = FIELD1 FIELD2 FIELD3 FIELD4
; EXPOSE_COLUMNS_PUT = FIELD1 FIELD2 FIELD3
;
EOT;
$configIniStr .= "\n\n";
foreach ($this->dbInfo as $table => $columns) {
$strColumns = implode(' ', $columns['columns']);
$configIniStr .= ";Rest Api for table $table with (".count($columns['columns']).") columns.\n";
$configIniStr .= "[$table]\n";
$configIniStr .= " ; Param to set allowed methods (separeted by a single space). Complete example: ALLOW_METHODS = GET POST PUT DELETE\n";
$configIniStr .= ";Table '$table' with ".count($columns['columns'])." columns.\n";
$configIniStr .= "[TABLE:$table]\n";
$configIniStr .= " ALLOW_METHODS = GET\n";
$configIniStr .= " ; Params to set columns that should be exposed, you can use wildcard '*' to specify all columns.\n";
$configIniStr .= " EXPOSE_COLUMNS_GET = *\n";
$configIniStr .= " EXPOSE_COLUMNS_POST = ".$strColumns."\n";
$configIniStr .= " EXPOSE_COLUMNS_PUT = ".$strColumns."\n";
@@ -99,7 +216,10 @@ class Service_Rest_RestTool
return $configFile;
}
public function buildApi()
/**
* Build Rest Crud Api
*/
public function buildCrudApi()
{
/**
* load configuration from /engine/config/rest-config.ini and
@@ -108,6 +228,23 @@ class Service_Rest_RestTool
$this->loadConfig();
$this->loadDbXmlSchema();
self::out('Output folder: ', 'info', false);
echo $this->basePath . "services/rest/crud";
if (! is_dir($this->basePath . "services/rest/crud/")) {
G::mk_dir($this->basePath . "services/rest/crud/");
echo ' (created)';
}
echo "\n\n";
if (! is_writable($this->basePath . "services/rest/crud/")) {
throw new Exception(fprintf(
"Runtime Error: Output folder '%s' is not writable.",
$this->basePath . "services/rest/crud/"
));
}
Haanga::configure(array(
'template_dir' => dirname(__FILE__) . '/templates/',
'cache_dir' => sys_get_temp_dir() . '/haanga_cache/',
@@ -121,11 +258,11 @@ class Service_Rest_RestTool
)
));
foreach ($this->config as $table => $conf) {
$c = 0;
foreach ($this->config['_tables'] as $table => $conf) {
$classname = self::camelize($table, 'class');
$allowedMethods = explode(' ', $conf['ALLOW_METHODS']);
$methods = '';
//$allowedMethods = array('DELETE');
foreach ($allowedMethods as $method) {
// validation for a valid method
@@ -231,11 +368,22 @@ class Service_Rest_RestTool
'methods' => $methods
), true);
echo "saving $classname.php\n";
//echo "File #$c - $classname.php saved!\n";
++$c;
file_put_contents($this->basePath . "services/rest/crud/$classname.php", $classContent);
}
printf("Done, generated %s Rest Class Files.\n\n", self::out("($c)", 'success', false, true));
}
/**
* Camilize a string
* Example: some_underscored_string to SomeUnderscoredString
*
* @param string $str string to camelze
* @param string $type if the type is 'var' do not capitalize the first character.
* @return string camelized string
*/
protected static function camelize($str, $type = 'var')
{
if (is_array($str)) {
@@ -253,6 +401,16 @@ class Service_Rest_RestTool
return $str;
}
/**
* Try convert a string to its native variable type
* Example:
* for a string 'true' => true
* for a string 'false' => false
* for a string '1.0' => 1.0
*
* @param string $value string to try cast to its native variable type
* @return mixed value converted to its native type, if wasn't possible the same string will be returned
*/
protected static function cast($value)
{
if ($value === 'true') {
@@ -265,6 +423,40 @@ class Service_Rest_RestTool
return $value;
}
/**
* colorize output
*/
static public function out($text, $color = null, $newLine = true, $ret = false)
{
if (DIRECTORY_SEPARATOR == '\\') {
$hasColorSupport = false !== getenv('ANSICON');
} else {
$hasColorSupport = true;
}
$styles = array(
'success' => "\033[0;32m%s\033[0m",
'error' => "\033[31;31m%s\033[0m",
'info' => "\033[33;33m%s\033[0m"
);
$format = '%s';
if (isset($styles[$color]) && $hasColorSupport) {
$format = $styles[$color];
}
if ($newLine) {
$format .= PHP_EOL;
}
if ($ret) {
return sprintf($format, $text);
} else {
printf($format, $text);
}
}
}