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 @@
///
///
/// - @ref UserManualActions
+///
+/// - @ref UserManualActionsIntro
+/// - @ref UserManualActionsHelloWorld
+/// - @ref UserManualActionsHelloJson
+/// - @ref UserManualActionsEcho
+///
///
///
////////////////////////////////////////////////////////////////////////////////
@@ -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
+ });
}
}