diff --git a/Doxygen/arango-html.doxy b/Doxygen/arango-html.doxy index 23544d86eb..3552f2c06d 100644 --- a/Doxygen/arango-html.doxy +++ b/Doxygen/arango-html.doxy @@ -1550,7 +1550,7 @@ HIDE_UNDOC_RELATIONS = YES # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) -HAVE_DOT = YES +HAVE_DOT = NO # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will diff --git a/Doxygen/arango.template b/Doxygen/arango.template index 5d976951b8..95f0c6d96b 100644 --- a/Doxygen/arango.template +++ b/Doxygen/arango.template @@ -1550,7 +1550,7 @@ HIDE_UNDOC_RELATIONS = YES # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) -HAVE_DOT = YES +HAVE_DOT = NO # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will diff --git a/arangod/Documentation/user-manual.dox b/arangod/Documentation/user-manual.dox index 7d8c06697a..fdd18628b0 100644 --- a/arangod/Documentation/user-manual.dox +++ b/arangod/Documentation/user-manual.dox @@ -347,6 +347,12 @@ /// /// //////////////////////////////////////////////////////////////////////////////// @@ -418,7 +424,7 @@ /// The client API or browser sends a HTTP request to the ArangoDB server and /// the server returns a HTTP response to the client. A HTTP requests consists /// of a method, normally @LIT{GET} or @LIT{POST} when using a browser, and a -/// request path like "/hello/world". For a real Web server there are a zillion +/// request path like @LIT{/hello/world}. For a real Web server there are a zillion /// of other thing to consider, we will ignore this for the moment. The HTTP /// response contains a content type, describing how to interpret the returned /// data, and the data itself. @@ -462,7 +468,7 @@ /// /// Now use the browser and access /// -/// @LIT{http://localhost:8529/hello/world"} +/// @LIT{http://localhost:8529/hello/world} /// /// @section UserManualActionsHelloJson A Hello World Example for JSON ////////////////////////////////////////////////////////////////////// @@ -562,6 +568,79 @@ /// "options": { } /// } /// @endcode +/// +/// Please note that +/// +/// @code +/// arangosh> db._routing.save({ +/// ........> path : "/hello/echo", +/// ........> callback: "org/arangodb/actions/echoRequest" }); +/// @endcode +/// +/// is a short-cut for +/// +/// @code +/// arangosh> db._routing.save({ +/// ........> path : "/hello/echo-long", +/// ........> callback: { +/// ........> type: "function", +/// ........> module: "org/arangodb/actions", +/// ........> function: "echoRequest" }}); +/// @endcode +/// +/// The verbose form allows you to pass options to the called function: +/// +/// @code +/// arangosh> a = db._routing.firstExample({path : "/hello/echo-long"}); +/// arangosh> a.callback.options = { option: "my option1" }; +/// arangosh> db._replace(a, a); +/// @endcode +/// +/// You should now see the options in the result. +/// +/// @code +/// { +/// "request": { +/// "prefix": "/hello/echo-long", +/// ... +/// }, +/// "options": { +/// "option": "my option1" +/// } +/// } +/// @endcode +/// +/// @section UserManualActionsDYO Define Your Own Callback +////////////////////////////////////////////////////////// +/// +/// You can define your own callbacks by adding a new module to ArangoDB. In +/// order to avoid name clashes modules should be named +/// +/// @LIT{tld/domain/modulename} +/// +/// where @LIT{domain.tld} is your domain name. For development you can store +/// your code in files in the filesystem, see @ref MODULES_PATH and @MODULES. +/// +/// However, when you are finished with the development you can rollout the +/// module code by storing it inside the @LIT{_modules} collection. +/// +/// Create a file @LIT{hello-world.js} with the following content: +/// +/// @code +/// var actions = require("org/arangodb/actions"); +/// +/// exports.helloWorld = function (req, res) { +/// res.contentType = "text/html"; +/// res.responseCode = actions.HTTP_OK; +/// res.body = "Hello World!"; +/// }; +/// @endcode +/// +/// Load this file as new module @LIT{de.triagens.hello-world} into the database +/// +/// @code +/// arangosh> require("internal").defineModule("de/triagens/hello-world", "hello-world.js"); +/// @endcode //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- diff --git a/js/actions/system/api-system.js b/js/actions/system/api-system.js index a82a31e56c..893ce35b2b 100644 --- a/js/actions/system/api-system.js +++ b/js/actions/system/api-system.js @@ -52,15 +52,9 @@ function Routing (req, res) { console.log("request = %s", JSON.stringify(req)); callbacks = actions.routing(req.requestType, req.suffix); - - for (i = 0; i < callbacks.length; ++i) { - console.log("callback %d = %s", i, callbacks[i]); - } - current = 0; next = function () { - var options = {}; var callback; if (callbacks.length <= current) { @@ -70,14 +64,13 @@ function Routing (req, res) { callback = callbacks[current++]; - console.error("trying callback #%d / %d: %s # %s", current, callbacks.length, typeof callback, callback); - if (callback == null) { - actions.resultNotImplemented(req, res, "not implemented '" + req.suffix.join("/") + "'"); + actions.resultNotImplemented(req, res, + "not implemented '" + req.suffix.join("/") + "'"); } else { req.prefix = callback.path; - callback.func(req, res, next, options); + callback.func(req, res, next, callback.options); } } diff --git a/js/common/bootstrap/js-modules.h b/js/common/bootstrap/js-modules.h index 5a7fe322c2..bca27622de 100644 --- a/js/common/bootstrap/js-modules.h +++ b/js/common/bootstrap/js-modules.h @@ -11,7 +11,8 @@ static string JS_common_bootstrap_modules = " SYS_LOAD, SYS_LOG, SYS_LOG_LEVEL, SYS_OUTPUT,\n" " SYS_PROCESS_STAT, SYS_READ, SYS_SPRINTF, SYS_TIME,\n" " SYS_START_PAGER, SYS_STOP_PAGER, ARANGO_QUIET, MODULES_PATH,\n" - " COLOR_OUTPUT, COLOR_OUTPUT_RESET, COLOR_BRIGHT, PRETTY_PRINT */\n" + " COLOR_OUTPUT, COLOR_OUTPUT_RESET, COLOR_BRIGHT, PRETTY_PRINT,\n" + " SYS_SHA256, SYS_WAIT, SYS_GETLINE */\n" "\n" "////////////////////////////////////////////////////////////////////////////////\n" "/// @brief JavaScript server functions\n" @@ -180,6 +181,20 @@ static string JS_common_bootstrap_modules = "};\n" "\n" "////////////////////////////////////////////////////////////////////////////////\n" + "/// @brief unloads module\n" + "////////////////////////////////////////////////////////////////////////////////\n" + "\n" + "Module.prototype.unloadAll = function () {\n" + " var path;\n" + "\n" + " for (path in ModuleCache) {\n" + " if (ModuleCache.hasOwnProperty(path)) {\n" + " this.unload(path);\n" + " }\n" + " }\n" + "};\n" + "\n" + "////////////////////////////////////////////////////////////////////////////////\n" "/// @brief top-level module\n" "////////////////////////////////////////////////////////////////////////////////\n" "\n" @@ -226,7 +241,7 @@ static string JS_common_bootstrap_modules = "////////////////////////////////////////////////////////////////////////////////\n" "\n" "////////////////////////////////////////////////////////////////////////////////\n" - "/// @brief fs module\n" + "/// @brief file-system module\n" "////////////////////////////////////////////////////////////////////////////////\n" "\n" "ModuleCache[\"/fs\"] = new Module(\"/fs\");\n" @@ -321,18 +336,19 @@ static string JS_common_bootstrap_modules = " }\n" "\n" "////////////////////////////////////////////////////////////////////////////////\n" - "/// @brief reads a file\n" + "/// @brief reads a file from the module path or the database\n" "////////////////////////////////////////////////////////////////////////////////\n" "\n" " internal.readFile = function (path) {\n" " var i;\n" + " var mc;\n" + " var n;\n" "\n" " // try to load the file\n" " var paths = internal.MODULES_PATH;\n" "\n" " for (i = 0; i < paths.length; ++i) {\n" " var p = paths[i];\n" - " var n;\n" "\n" " if (p === \"\") {\n" " n = \".\" + path + \".js\";\n" @@ -342,7 +358,18 @@ static string JS_common_bootstrap_modules = " }\n" "\n" " if (fs.exists(n)) {\n" - " return { path : n, content : SYS_READ(n) };\n" + " return { path : n, content : internal.read(n) };\n" + " }\n" + " }\n" + "\n" + " // try to load the module from the database\n" + " mc = internal.db._collection(\"_modules\");\n" + "\n" + " if (mc !== null) {\n" + " n = mc.firstExample({ path: path });\n" + "\n" + " if (n !== null) {\n" + " return { path : \"_collection/\" + path, content : n.module };\n" " }\n" " }\n" "\n" @@ -353,7 +380,7 @@ static string JS_common_bootstrap_modules = " };\n" "\n" "////////////////////////////////////////////////////////////////////////////////\n" - "/// @brief loads a file\n" + "/// @brief loads a file from the file-system\n" "////////////////////////////////////////////////////////////////////////////////\n" "\n" " internal.loadFile = function (path) {\n" @@ -385,6 +412,35 @@ static string JS_common_bootstrap_modules = " };\n" "\n" "////////////////////////////////////////////////////////////////////////////////\n" + "/// @brief defines a module\n" + "////////////////////////////////////////////////////////////////////////////////\n" + "\n" + " internal.defineModule = function (path, file) {\n" + " var content;\n" + " var m;\n" + " var mc;\n" + "\n" + " content = internal.read(file);\n" + "\n" + " mc = internal.db._collection(\"_modules\");\n" + "\n" + " if (mc === null) {\n" + " throw \"you need to upgrade your database using 'arango-upgrade'\";\n" + " }\n" + "\n" + " path = module.normalise(path);\n" + " m = mc.firstExample({ path: path });\n" + "\n" + " if (m === null) {\n" + " mc.save({ path: path, module: content });\n" + " }\n" + " else {\n" + " m.content = content;\n" + " mc.replace(m, m);\n" + " }\n" + " };\n" + "\n" + "////////////////////////////////////////////////////////////////////////////////\n" "/// @}\n" "////////////////////////////////////////////////////////////////////////////////\n" "\n" @@ -534,6 +590,10 @@ static string JS_common_bootstrap_modules = "\n" "}());\n" "\n" + "// -----------------------------------------------------------------------------\n" + "// --SECTION-- END-OF-FILE\n" + "// -----------------------------------------------------------------------------\n" + "\n" "// Local Variables:\n" "// mode: outline-minor\n" "// outline-regexp: \"^\\\\(/// @brief\\\\|/// @addtogroup\\\\|// --SECTION--\\\\|/// @page\\\\|/// @}\\\\)\"\n" diff --git a/js/common/bootstrap/modules.js b/js/common/bootstrap/modules.js index 5e149ad54a..8824c6af9c 100644 --- a/js/common/bootstrap/modules.js +++ b/js/common/bootstrap/modules.js @@ -10,7 +10,8 @@ SYS_LOAD, SYS_LOG, SYS_LOG_LEVEL, SYS_OUTPUT, SYS_PROCESS_STAT, SYS_READ, SYS_SPRINTF, SYS_TIME, SYS_START_PAGER, SYS_STOP_PAGER, ARANGO_QUIET, MODULES_PATH, - COLOR_OUTPUT, COLOR_OUTPUT_RESET, COLOR_BRIGHT, PRETTY_PRINT */ + COLOR_OUTPUT, COLOR_OUTPUT_RESET, COLOR_BRIGHT, PRETTY_PRINT, + SYS_SHA256, SYS_WAIT, SYS_GETLINE */ //////////////////////////////////////////////////////////////////////////////// /// @brief JavaScript server functions @@ -178,6 +179,20 @@ Module.prototype.unload = function (path) { delete ModuleCache[norm]; }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief unloads module +//////////////////////////////////////////////////////////////////////////////// + +Module.prototype.unloadAll = function () { + var path; + + for (path in ModuleCache) { + if (ModuleCache.hasOwnProperty(path)) { + this.unload(path); + } + } +}; + //////////////////////////////////////////////////////////////////////////////// /// @brief top-level module //////////////////////////////////////////////////////////////////////////////// @@ -225,7 +240,7 @@ function require (path) { //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -/// @brief fs module +/// @brief file-system module //////////////////////////////////////////////////////////////////////////////// ModuleCache["/fs"] = new Module("/fs"); @@ -320,18 +335,19 @@ ModuleCache["/internal"] = new Module("/internal"); } //////////////////////////////////////////////////////////////////////////////// -/// @brief reads a file +/// @brief reads a file from the module path or the database //////////////////////////////////////////////////////////////////////////////// internal.readFile = function (path) { var i; + var mc; + var n; // try to load the file var paths = internal.MODULES_PATH; for (i = 0; i < paths.length; ++i) { var p = paths[i]; - var n; if (p === "") { n = "." + path + ".js"; @@ -341,7 +357,18 @@ ModuleCache["/internal"] = new Module("/internal"); } if (fs.exists(n)) { - return { path : n, content : SYS_READ(n) }; + return { path : n, content : internal.read(n) }; + } + } + + // try to load the module from the database + mc = internal.db._collection("_modules"); + + if (mc !== null) { + n = mc.firstExample({ path: path }); + + if (n !== null) { + return { path : "_collection/" + path, content : n.module }; } } @@ -352,7 +379,7 @@ ModuleCache["/internal"] = new Module("/internal"); }; //////////////////////////////////////////////////////////////////////////////// -/// @brief loads a file +/// @brief loads a file from the file-system //////////////////////////////////////////////////////////////////////////////// internal.loadFile = function (path) { @@ -383,6 +410,35 @@ ModuleCache["/internal"] = new Module("/internal"); + internal.MODULES_PATH + "'"; }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief defines a module +//////////////////////////////////////////////////////////////////////////////// + + internal.defineModule = function (path, file) { + var content; + var m; + var mc; + + content = internal.read(file); + + mc = internal.db._collection("_modules"); + + if (mc === null) { + throw "you need to upgrade your database using 'arango-upgrade'"; + } + + path = module.normalise(path); + m = mc.firstExample({ path: path }); + + if (m === null) { + mc.save({ path: path, module: content }); + } + else { + m.content = content; + mc.replace(m, m); + } + }; + //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// @@ -533,6 +589,10 @@ ModuleCache["/console"] = new Module("/console"); }()); +// ----------------------------------------------------------------------------- +// --SECTION-- END-OF-FILE +// ----------------------------------------------------------------------------- + // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" diff --git a/js/server/modules/org/arangodb/actions.js b/js/server/modules/org/arangodb/actions.js index ef462f9073..9aeb57a253 100644 --- a/js/server/modules/org/arangodb/actions.js +++ b/js/server/modules/org/arangodb/actions.js @@ -92,6 +92,33 @@ function LookupCallbackString (callback) { return undefined; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief looks up a callback for a function +//////////////////////////////////////////////////////////////////////////////// + +function LookupCallbackFunction (callback) { + var module; + var fn; + + try { + module = require(callback.module); + } + catch (err) { + console.error("cannot load callback '%s' from module '%s': %s", + fn, callback.module, "" + err); + return undefined; + } + + fn = callback.function; + + if (fn in module) { + return module[fn]; + } + + console.error("callback '%s' is not defined in module '%s'", fn, module); + return undefined; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief looks up a callback for static data //////////////////////////////////////////////////////////////////////////////// @@ -124,6 +151,9 @@ function LookupCallback (callback) { if (type === "static") { return LookupCallbackStatic(callback); } + else if (type === "function") { + return LookupCallbackFunction(callback); + } else { console.error("unknown callback type '%s'", type); return undefined; @@ -431,13 +461,20 @@ function ReloadRouting () { var cb = LookupCallback(callback[i]); if (cb === undefined) { - console.error("route '%s' contains invalid callback '%s'", route._id, JSON.stringify(callback[i])); + console.error("route '%s' contains invalid callback '%s'", + route._id, JSON.stringify(callback[i])); } else if (typeof cb !== "function") { - console.error("route '%s' contains non-function callback '%s'", route._id, JSON.stringify(callback[i])); + console.error("route '%s' contains non-function callback '%s'", + route._id, JSON.stringify(callback[i])); } else { - tmp.push(cb); + var result = { + func: cb, + options: callback[i].options || {} + }; + + tmp.push(result); } } @@ -588,7 +625,11 @@ function Routing (method, path) { var callback = td.callback; for (j = 0; j < callback.length; ++j) { - result.push({ func : callback[j], path : td.path }); + result.push({ + func: callback[j].func, + options: callback[j].options, + path: td.path + }); } } @@ -597,7 +638,11 @@ function Routing (method, path) { var callback = bu.callback; for (j = 0; j < callback.length; ++j) { - result.push({ func : callback[j], path : bu.path }); + result.push({ + func: callback[j].func, + options: callback[j].options, + path: bu.path + }); } }