From 620243fc18c91956e42d44e6024b832baa579c2d Mon Sep 17 00:00:00 2001 From: Michael Hackstein Date: Mon, 19 Jan 2015 15:08:24 +0100 Subject: [PATCH] The new modules arangoApp and routing now are able to create an app including a context. Also the routing information is created in the routing file. Now has to be included into the routing table --- js/common/bootstrap/modules.js | 4 +- .../modules/org/arangodb/foxx/arangoApp.js | 14 +- .../modules/org/arangodb/foxx/manager.js | 1337 +---------------- .../modules/org/arangodb/foxx/routing.js | 696 +++++---- 4 files changed, 429 insertions(+), 1622 deletions(-) diff --git a/js/common/bootstrap/modules.js b/js/common/bootstrap/modules.js index bf0e5b6e4d..4418d2ce3c 100644 --- a/js/common/bootstrap/modules.js +++ b/js/common/bootstrap/modules.js @@ -1357,8 +1357,8 @@ function require (path) { Module.prototype.createAppModule = function (app) { 'use strict'; - var libpath = fs.join(this._root, this._path); - if (this._manifest.hasOwnProperty("lib")) { + var libpath = fs.join(app._root, app._path); + if (app._manifest.hasOwnProperty("lib")) { libpath = fs.join(libpath, app._manifest.lib); } var pkg = new Package("application-package", diff --git a/js/server/modules/org/arangodb/foxx/arangoApp.js b/js/server/modules/org/arangodb/foxx/arangoApp.js index fdc1daf60a..8e0d4768a3 100644 --- a/js/server/modules/org/arangodb/foxx/arangoApp.js +++ b/js/server/modules/org/arangodb/foxx/arangoApp.js @@ -108,7 +108,6 @@ //////////////////////////////////////////////////////////////////////////////// var ArangoApp = function (config) { - var collectionPrefix = this._mount.substr(1).replace(/-/g, "_").replace(/\//g, "_") + "_"; this._id = config.id; // ??? this._manifest = config.manifest; this._name = config.manifest.name; @@ -120,11 +119,7 @@ var ArangoApp = function (config) { this._isSystem = config.isSystem || false; this._isDevelopment = config.isDevelopment || false; this._exports = {}; - - // converts the mount point into the default prefix - - this._collectionPrefix = collectionPrefix; - + this._collectionPrefix = this._mount.substr(1).replace(/-/g, "_").replace(/\//g, "_") + "_"; this._context = new AppContext(this); }; @@ -188,7 +183,12 @@ var ArangoApp = function (config) { //////////////////////////////////////////////////////////////////////////////// ArangoApp.prototype.loadAppScript = function (filename, options) { - var appContext = _.merge(options.context || {}, this._context); + var appContext; + if (options !== undefined && options.hasOwnProperty("appContext")) { + appContext = _.extend(options.appContext, this._context); + } else { + appContext = _.extend({}, this._context); + } options = options || {}; diff --git a/js/server/modules/org/arangodb/foxx/manager.js b/js/server/modules/org/arangodb/foxx/manager.js index a5411c404d..154832cdf8 100644 --- a/js/server/modules/org/arangodb/foxx/manager.js +++ b/js/server/modules/org/arangodb/foxx/manager.js @@ -1,4 +1,4 @@ -/*jshint strict: false */ +/*jshint strict: false*/ /*global module, require, exports */ //////////////////////////////////////////////////////////////////////////////// @@ -24,63 +24,59 @@ /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// -/// @author Dr. Frank Celler, Michael Hackstein +/// @author Dr. Frank Celler +/// @author Michael Hackstein /// @author Copyright 2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// -/* -var arangodb = require("org/arangodb"); -var ArangoError = arangodb.ArangoError; -var errors = arangodb.errors; -var console = require("console"); -var fs = require("fs"); -var utils = require("org/arangodb/foxx/manager-utils"); -var store = require("org/arangodb/foxx/store"); +(function() { + "use strict"; +// ----------------------------------------------------------------------------- +// --CHAPTER-- used code +// ----------------------------------------------------------------------------- -var _ = require("underscore"); +// ----------------------------------------------------------------------------- +// --SECTION-- imports +// ----------------------------------------------------------------------------- -var executeGlobalContextFunction = require("internal").executeGlobalContextFunction; -var frontendDevelopmentMode = require("internal").frontendDevelopmentMode; -var checkParameter = arangodb.checkParameter; -var preprocess = require("org/arangodb/foxx/preprocessor").preprocess; + var fs = require("fs"); + var utils = require("org/arangodb/foxx/manager-utils"); + var store = require("org/arangodb/foxx/store"); + var console = require("console"); + var ArangoApp = require("org/arangodb/foxx/arangoApp").ArangoApp; + var routeApp = require("org/arangodb/foxx/routing").routeApp; + var arangodb = require("org/arangodb"); + var ArangoError = arangodb.ArangoError; + var checkParameter = arangodb.checkParameter; + var errors = arangodb.errors; + var download = require("internal").download; -var developmentMode = require("internal").developmentMode; + var throwDownloadError = arangodb.throwDownloadError; + var throwFileNotFound = arangodb.throwFileNotFound; -var download = require("internal").download; -var throwDownloadError = arangodb.throwDownloadError; -var throwFileNotFound = arangodb.throwFileNotFound; +// ----------------------------------------------------------------------------- +// --SECTION-- private variables +// ----------------------------------------------------------------------------- +var appCache = {}; // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// -/// @brief returns the transform script +/// @brief lookup app in cache +/// Returns either the app or undefined if it is not cached. //////////////////////////////////////////////////////////////////////////////// -function transformScript (file) { - "use strict"; - - if (/\.coffee$/.test(file)) { - return function (content) { - return preprocess(content, "coffee"); - }; +var lookupApp = function(mount) { + if (!appCache.hasOwnProperty(mount)) { + throw "App not found"; } + return appCache[mount]; +}; - return preprocess; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns the aal collection -//////////////////////////////////////////////////////////////////////////////// - -function getStorage () { - "use strict"; - - return arangodb.db._collection('_aal'); -} //////////////////////////////////////////////////////////////////////////////// /// @brief check a manifest for completeness @@ -88,9 +84,7 @@ function getStorage () { /// this implements issue #590: Manifest Lint //////////////////////////////////////////////////////////////////////////////// -function checkManifest (filename, mf) { - "use strict"; - +var checkManifest = function(filename, mf) { // add some default attributes if (! mf.hasOwnProperty("author")) { // add a default (empty) author @@ -129,13 +123,14 @@ function checkManifest (filename, mf) { }; var att, failed = false; + var expectedType, actualType; for (att in expected) { if (expected.hasOwnProperty(att)) { if (mf.hasOwnProperty(att)) { // attribute is present in manifest, now check data type - var expectedType = expected[att][1]; - var actualType = Array.isArray(mf[att]) ? "array" : typeof(mf[att]); + expectedType = expected[att][1]; + actualType = Array.isArray(mf[att]) ? "array" : typeof(mf[att]); if (actualType !== expectedType) { console.error("Manifest '%s' uses an invalid data type (%s) for %s attribute '%s'", @@ -177,1071 +172,9 @@ function checkManifest (filename, mf) { } } } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief finds mount document from mount path or identifier -//////////////////////////////////////////////////////////////////////////////// - -function mountFromId (mount) { - "use strict"; - - var aal = getStorage(); - var doc = aal.firstExample({ type: "mount", _id: mount }); - - if (doc === null) { - doc = aal.firstExample({ type: "mount", _key: mount }); - } - - if (doc === null) { - doc = aal.firstExample({ type: "mount", mount: mount }); - } - - if (doc === null) { - throw new Error("Cannot find mount identifier or path '" + mount + "'"); - } - - return doc; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief builds one asset of an app -//////////////////////////////////////////////////////////////////////////////// - -function buildAssetContent (app, assets, basePath) { - "use strict"; - - var i; - var j; - var m; - - var excludeFile = function (name) { - var parts = name.split('/'); - - if (parts.length > 0) { - var last = parts[parts.length - 1]; - - // exclude all files starting with . - if (last[0] === '.') { - return true; - } - } - - return false; - }; - - var reSub = /(.*)\/\*\*$/; - var reAll = /(.*)\/\*$/; - - var files = []; - - for (j = 0; j < assets.length; ++j) { - var asset = assets[j]; - var match = reSub.exec(asset); - - if (match !== null) { - m = fs.listTree(fs.join(basePath, match[1])); - - // files are sorted in file-system order. - // this makes the order non-portable - // we'll be sorting the files now using JS sort - // so the order is more consistent across multiple platforms - m.sort(); - - for (i = 0; i < m.length; ++i) { - var filename = fs.join(basePath, match[1], m[i]); - - if (! excludeFile(m[i])) { - if (fs.isFile(filename)) { - files.push(filename); - } - } - } - } - else { - match = reAll.exec(asset); - - if (match !== null) { - throw new Error("Not implemented"); - } - else { - if (! excludeFile(asset)) { - files.push(fs.join(basePath, asset)); - } - } - } - } - - var content = ""; - - for (i = 0; i < files.length; ++i) { - try { - var c = fs.read(files[i]); - - content += c + "\n"; - } - catch (err) { - console.error("Cannot read asset '%s'", files[i]); - } - } - - return content; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief installs an asset for an app -//////////////////////////////////////////////////////////////////////////////// - -function buildFileAsset (app, path, basePath, asset) { - "use strict"; - - var content = buildAssetContent(app, asset.files, basePath); - var type; - - // ............................................................................. - // content-type detection - // ............................................................................. - - // contentType explicitly specified for asset - if (asset.hasOwnProperty("contentType") && asset.contentType !== '') { - type = asset.contentType; - } - - // path contains a dot, derive content type from path - else if (path.match(/\.[a-zA-Z0-9]+$/)) { - type = arangodb.guessContentType(path); - } - - // path does not contain a dot, - // derive content type from included asset names - else if (asset.files.length > 0) { - type = arangodb.guessContentType(asset.files[0]); - } - - // use built-in defaulti content-type - else { - type = arangodb.guessContentType(""); - } - - // ............................................................................. - // return content - // ............................................................................. - - return { contentType: type, body: content }; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief generates development asset action -//////////////////////////////////////////////////////////////////////////////// - -function buildDevelopmentAssetRoute (app, path, basePath, asset) { - "use strict"; - return { - url: { match: path }, - action: { - callback: function (req, res) { - var c = buildFileAsset(app, path, basePath, asset); - - res.contentType = c.contentType; - res.body = c.body; - } - } - }; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief generates asset action -//////////////////////////////////////////////////////////////////////////////// - -function buildAssetRoute (app, path, basePath, asset) { - "use strict"; - - var c = buildFileAsset(app, path, basePath, asset); - - return { - url: { match: path }, - content: { contentType: c.contentType, body: c.body } - }; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief installs the assets of an app -//////////////////////////////////////////////////////////////////////////////// - -function installAssets (app, routes) { - "use strict"; - - var path; - - var desc = app._manifest; - - if (! desc) { - throw new Error("Invalid application manifest"); - } - - var normalized; - var route; - - if (desc.hasOwnProperty('assets')) { - for (path in desc.assets) { - if (desc.assets.hasOwnProperty(path)) { - var asset = desc.assets[path]; - var basePath = fs.join(app._root, app._path); - - if (asset.hasOwnProperty('basePath')) { - basePath = asset.basePath; - } - - normalized = arangodb.normalizeURL("/" + path); - - if (asset.hasOwnProperty('files')) { - if (frontendDevelopmentMode) { - route = buildDevelopmentAssetRoute(app, normalized, basePath, asset); - } - else { - route = buildAssetRoute(app, normalized, basePath, asset); - } - - routes.routes.push(route); - } - } - } - } - - if (desc.hasOwnProperty('files')) { - for (path in desc.files) { - if (desc.files.hasOwnProperty(path)) { - var directory = desc.files[path]; - - normalized = arangodb.normalizeURL("/" + path); - - route = { - url: { match: normalized + "/*" }, - action: { - "do": "org/arangodb/actions/pathHandler", - "options": { - root: app._root, - path: fs.join(app._path, directory) - } - } - }; - - routes.routes.push(route); - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief creates an app entry -/// upsert = insert + update -//////////////////////////////////////////////////////////////////////////////// - -function upsertAalAppEntry (manifest, thumbnail, path) { - "use strict"; - - var aal = getStorage(); - var doc = aal.firstExample({ - type: "app", - name: manifest.name, - version: manifest.version - }); - - if (doc === null) { - // no previous entry: save - aal.save({ - type: "app", - app: "app:" + manifest.name + ":" + manifest.version, - name: manifest.name, - author: manifest.author, - description: manifest.description, - version: manifest.version, - path: path, - manifest: manifest, - thumbnail: thumbnail, - isSystem: manifest.isSystem || false - }); - } - else { - // check if something was changed - if (JSON.stringify(manifest) !== JSON.stringify(doc.manifest) || - path !== doc.path || - thumbnail !== doc.thumbnail) { - - doc.description = manifest.description; - doc.path = path; - doc.manifest = manifest; - doc.thumbnail = thumbnail; - - aal.replace(doc, doc); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief mounts an app -//////////////////////////////////////////////////////////////////////////////// - -function mountAalApp (app, mount, options) { - "use strict"; - - var aal = getStorage(); - - // ............................................................................. - // check that the mount path is free - // ............................................................................. - - var find = aal.firstExample({ type: "mount", mount: mount, active: true }); - - if (find !== null) { - throw new Error("Cannot use mount path '" + mount + "', already used by '" - + find.app + "' (" + find._key + ")"); - } - - // ............................................................................. - // check the prefix - // ............................................................................. - - var prefix = options.collectionPrefix; - - if (prefix === undefined) { - options = _.clone(options); - options.collectionPrefix = prefix = prefixFromMount(mount); - } - - // ............................................................................. - // create a new (unique) entry in aal - // ............................................................................. - - var desc = { - type: "mount", - app: app._id, - name: app._name, - description: app._manifest.description, - repository: app._manifest.repository, - license: app._manifest.license, - author: app._manifest.author, - contributors: app._manifest.contributors, - mount: mount, - active: true, - error: false, - isSystem: app._manifest.isSystem || false, - options: options - }; - - return aal.save(desc); -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief scans fetched Foxx applications -//////////////////////////////////////////////////////////////////////////////// - -function scanDirectory (path) { - "use strict"; - - var j; - - if (path === "undefined") { - return; - } - - var files = fs.list(path); - - // note: files do not have a determinstic order, but it doesn't matter here - // as we're treating individual Foxx apps and their order is irrelevant - - // Variables required in loop - var m, mf, thumbnail, p; - - for (j = 0; j < files.length; ++j) { - m = fs.join(path, files[j], "manifest.json"); - thumbnail = undefined; - mf = validateManifestFile(m); - - if (mf !== undefined) { - try { - if (mf.hasOwnProperty('thumbnail') && mf.thumbnail !== null && mf.thumbnail !== '') { - p = fs.join(path, files[j], mf.thumbnail); - - try { - thumbnail = fs.read64(p); - } - catch (err2) { - console.warnLines( - "Cannot read thumbnail %s referenced by manifest '%s': %s", p, m, err2); - } - } - - upsertAalAppEntry(mf, thumbnail, files[j]); - } - catch (err) { - console.errorLines( - "Cannot read app manifest '%s': %s", m, String(err.stack || err)); - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief create configuration -//////////////////////////////////////////////////////////////////////////////// - -function checkConfiguration (app, options) { - "use strict"; - - if (options === undefined || options === null) { - options = {}; - } - - if (! options.hasOwnProperty("configuration")) { - options.configuration = {}; - } - - if (! app._manifest.hasOwnProperty("configuration")) { - return options; - } - - var configuration = options.configuration; - var expected = app._manifest.configuration; - var att; - - for (att in expected) { - if (expected.hasOwnProperty(att)) { - if (configuration.hasOwnProperty(att)) { - var value = configuration[att]; - var expectedType = expected[att].type; - var actualType = Array.isArray(value) ? "array" : typeof(value); - - if (expectedType === "integer" && actualType === "number") { - actualType = (value === Math.floor(value) ? "integer" : "number"); - } - - if (actualType !== expectedType) { - throw new Error( - "configuration for '" + app._manifest.name + "' uses " - + "an invalid data type (" + actualType + ") " - + "for " + expectedType + " attribute '" + att + "'"); - } - } - else if (expected[att].hasOwnProperty("default")) { - configuration[att] = expected[att]["default"]; - } - else { - throw new Error( - "configuration for '" + app._manifest.name + "' is " - + "missing a value for attribute '" + att + "'"); - } - } - } - - // additionally check if there are superfluous attributes in the manifest - for (att in configuration) { - if (configuration.hasOwnProperty(att)) { - if (! expected.hasOwnProperty(att)) { - console.warn("configuration for '%s' contains an unknown attribute '%s'", - app._manifest.name, - att); - } - } - } - - return options; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns mount point for system apps -//////////////////////////////////////////////////////////////////////////////// - - function systemMountPoint (appName) { - "use strict"; - - if (appName === "aardvark") { - return "/_admin/aardvark"; - } - - if (appName === "gharial") { - return "/_api/gharial"; - } - - if (appName === "cerberus") { - return "/_system/cerberus"; - } - - return false; - } - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns collection prefix for system apps -//////////////////////////////////////////////////////////////////////////////// - - function systemCollectionPrefix (appName) { - "use strict"; - - if (appName === "sessions") { - return "_"; - } - - if (appName === "users") { - return "_"; - } - - return false; - } - -// ----------------------------------------------------------------------------- -// --SECTION-- public functions -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief scans fetched Foxx applications -//////////////////////////////////////////////////////////////////////////////// - -exports.scanAppDirectory = function () { - "use strict"; - - var aal = getStorage(); - - // only initialize global collections - if (aal === null) { - return; - } - - // remove all loaded apps first - aal.removeByExample({ type: "app" }); - - // now re-scan, starting with system apps - scanDirectory(module.systemAppPath()); - - // now scan database-specific apps - scanDirectory(module.appPath()); }; -//////////////////////////////////////////////////////////////////////////////// -/// @brief rescans the Foxx application directory -/// this function is a trampoline for scanAppDirectory -/// the shorter function name is only here to keep compatibility with the -/// client-side Foxx manager -//////////////////////////////////////////////////////////////////////////////// -exports.rescan = function () { - "use strict"; - - return exports.scanAppDirectory(); -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief mounts a Foxx application -/// -/// Input: -/// * appId: the application identifier -/// * mount: the mount path starting with a "/" -/// * options: -/// collectionPrefix: overwrites the default prefix -/// reload: reload the routing info (default: true) -/// configuration: configuration options -/// -/// Output: -/// * appId: the application identifier (must be mounted) -/// * mountId: the mount identifier -//////////////////////////////////////////////////////////////////////////////// - -exports.mount = function (appId, mount, options) { - "use strict"; - - checkParameter( - "mount(, , [])", - [ [ "Application identifier", "string" ], - [ "Mount path", "string" ] ], - [ appId, mount ] ); - - // ............................................................................. - // mark that app has an error and cannot be mounted - // ............................................................................. - - function markAsIllegal (doc, err) { - if (doc !== undefined) { - var aal = getStorage(); - var desc = aal.document(doc._key)._shallowCopy; - - desc.error = String(err.stack || err); - desc.active = false; - - aal.replace(doc, desc); - } - } - - // ............................................................................. - // locate the application - // ............................................................................. - - if (appId.substr(0,4) !== "app:") { - appId = "app:" + appId + ":latest"; - } - - var app = createApp(appId, options); - - if (app === null) { - throw new Error("Cannot find application '" + appId + "'"); - } - - // ............................................................................. - // install the application - // ............................................................................. - - options = checkConfiguration(app, options); - - var doc; - - try { - doc = mountAalApp(app, mount, options); - } - catch (err) { - markAsIllegal(doc, err); - throw err; - } - - // ............................................................................. - // setup & reload - // ............................................................................. - - if (typeof options.setup !== "undefined" && options.setup === true) { - try { - exports.setup(mount); - } - catch (err2) { - markAsIllegal(doc, err2); - throw err2; - } - } - - if (typeof options.reload === "undefined" || options.reload === true) { - executeGlobalContextFunction("reloadRouting"); - } - - return { appId: app._id, mountId: doc._key, mount: mount }; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief unmounts a Foxx application -/// -/// Input: -/// * key: mount key or mount point -/// -/// Output: -/// * appId: the application identifier -/// * mount: the mount path starting with "/" -/// * collectionPrefix: the collection prefix -//////////////////////////////////////////////////////////////////////////////// - -exports.unmount = function (mount) { - "use strict"; - - checkParameter( - "unmount()", - [ [ "Mount identifier", "string" ] ], - [ mount ] ); - - var doc = mountFromId(mount); - - if (doc.isSystem && mount.charAt(1) === '_') { - throw new Error("Cannot unmount system application"); - } - - getStorage().remove(doc); - - executeGlobalContextFunction("reloadRouting"); - - return { appId: doc.app, mount: doc.mount, options: doc.options }; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returnes git information of a Foxx application -/// -/// Input: -/// * name: application name -/// -/// Output: -/// * name: application name -/// * git: git information -//////////////////////////////////////////////////////////////////////////////// - -exports.gitinfo = function (key) { - "use strict"; - - var _ = require("underscore"), gitinfo, - aal = getStorage(), - result = aal.toArray().concat(exports.developmentMounts()), - path = module.appPath(), - foxxPath, completePath, gitfile, gitcontent; - - _.each(result, function(k) { - - if (k.name === key) { - foxxPath = k.path; - } - }); - - completePath = path+"/"+foxxPath; - gitfile = completePath + "/gitinfo.json"; - - if (fs.isFile(gitfile)) { - gitcontent = fs.read(gitfile); - gitinfo = {git: true, url: JSON.parse(gitcontent), name: key}; - } - else { - gitinfo = {}; - } - - return gitinfo; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returnes mount points of a Foxx application -/// -/// Input: -/// * name: application name -/// -/// Output: -/// * name: application name -/// * mount: the mount path -//////////////////////////////////////////////////////////////////////////////// - -exports.mountinfo = function (key) { - "use strict"; - - var _ = require("underscore"), mountinfo = []; - - if (key === undefined) { - _.each(exports.appRoutes(), function(m) { - mountinfo.push({name: m.appContext.name, mount: m.appContext.mount}); - }); - } - else { - _.each(exports.appRoutes(), function(m) { - if (m.appContext.name === key) { - mountinfo.push({name: m.appContext.name, mount: m.appContext.mount}); - } - }); - } - - return mountinfo; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief purges a Foxx application -/// -/// Input: -/// * name: application name -/// -/// Output: -/// * appId: the application identifier -/// * mount: the mount path starting with "/" -/// * collectionPrefix: the collection prefix -//////////////////////////////////////////////////////////////////////////////// - -exports.purge = function (key) { - "use strict"; - - checkParameter( - "purge()", - [ [ "app-id or name", "string" ] ], - [ key ] ); - - var doc = getStorage().firstExample({ type: "app", app: key }); - - if (doc === null) { - doc = getStorage().firstExample({ type: "app", name: key }); - } - - if (doc === null) { - throw new Error("Cannot find application '" + key + "'"); - } - - // system apps cannot be removed - if (doc.isSystem) { - throw new Error("Cannot purge system application"); - } - - var purged = [ ]; - - var cursor = getStorage().byExample({ type: "mount", app: doc.app }); - - while (cursor.hasNext()) { - var mount = cursor.next(); - - exports.teardown(mount.mount); - exports.unmount(mount.mount); - - purged.push(mount.mount); - } - - // remove the app - getStorage().remove(doc); - - executeGlobalContextFunction("reloadRouting"); - - // we can be sure this is a database-specific app and no system app - var path = fs.join(module.appPath(), doc.path); - fs.removeDirectoryRecursive(path, true); - - return { appId: doc.app, name: doc.name, purged: purged }; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief sets up a development app -/// -/// Input: -/// * filename: the directory name of the development app -/// -/// Output: -/// - -//////////////////////////////////////////////////////////////////////////////// - -exports.devSetup = function (filename) { - "use strict"; - - checkParameter( - "devSetup()", - [ [ "Application folder", "string" ] ], - [ filename ] ); - - var root = module.devAppPath(); - var m = fs.join(root, filename, "manifest.json"); - - var mf = validateManifestFile(m); - if (mf !== undefined) { - var appId = "dev:" + mf.name + ":" + filename; - var mount = "/dev/" + filename; - var prefix = prefixFromMount(mount); - var app = createApp(appId); - if (app !== undefined) { - try { - setupApp(app, mount, prefix); - } catch (err) { - console.errorLines( - "Setup not possible for dev app '%s': %s", appId, String(err.stack || err)); - throw err; - } - } - } else { - throw new Error("Cannot find manifest file '" + m + "'"); - } -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief tears down up a development app -/// -/// Input: -/// * filename: the directory name of the development app -/// -/// Output: -/// - -//////////////////////////////////////////////////////////////////////////////// - -exports.devTeardown = function (filename) { - "use strict"; - - checkParameter( - "devTeardown()", - [ [ "Application folder", "string" ] ], - [ filename ] ); - - var root = module.devAppPath(); - var m = fs.join(root, filename, "manifest.json"); - var mf = validateManifestFile(m); - if (mf !== undefined) { - var appId = "dev:" + mf.name + ":" + filename; - var mount = "/dev/" + filename; - var prefix = prefixFromMount(mount); - var app = createApp(appId); - if (app !== undefined) { - try { - teardownApp(app, mount, prefix); - } catch (err) { - console.errorLines( - "Teardown not possible for dev App '%s': %s", appId, String(err.stack || err)); - throw err; - } - } - } else { - throw new Error("Cannot find manifest file '" + m + "'"); - } -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns the app for a mount path -//////////////////////////////////////////////////////////////////////////////// - -exports.mountedApp = function (path) { - if (MOUNTED_APPS.hasOwnProperty(path)) { - return MOUNTED_APPS[path]._exports; - } - - return {}; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief builds a github repository URL -//////////////////////////////////////////////////////////////////////////////// - -exports.buildGithubUrl = function (repository, version) { - "use strict"; - - if (typeof version === "undefined") { - version = "master"; - } - - return 'https://github.com/' + repository + '/archive/' + version + '.zip'; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief fetches a foxx app from a remote repository -//////////////////////////////////////////////////////////////////////////////// - -exports.fetchFromGithub = function (url, name, version) { - - var source = { - location: url, - name: name, - version: version - }; - utils.processGithubRepository(source); - var realFile = source.filename; - - var appPath = module.appPath(); - if (appPath === undefined) { - fs.remove(realFile); - throw "javascript.app-path not set, rejecting app loading"; - } - var path = fs.join(appPath, source.name + "-" + source.version); - - if (fs.exists(path)) { - fs.remove(realFile); - return "app:" + source.name + ":" + source.version; - } - - fs.makeDirectoryRecursive(path); - fs.unzipFile(realFile, path, false, true); - - var gitFilename = "/gitinfo.json"; - fs.write(path+gitFilename, JSON.stringify(url)); - - exports.scanAppDirectory(); - - return "app:" + source.name + ":" + source.version; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief initializes the Foxx apps -//////////////////////////////////////////////////////////////////////////////// - -exports.initializeFoxx = function () { - "use strict"; - - try { - exports.scanAppDirectory(); - } - catch (err) { - console.errorLines("cannot initialize Foxx application: %s", String(err.stack || err)); - } - - var aal = getStorage(); - - if (aal !== null) { - var systemAppPath = module.systemAppPath(); - - var fs = require("fs"); - var apps = fs.list(systemAppPath); - - // make sure the aardvark app is always there - if (apps.indexOf("aardvark") === -1) { - apps.push("aardvark"); - } - - apps.forEach(function (appName) { - var mount = systemMountPoint(appName); - - // for all unknown system apps: check that the directory actually exists - if (! mount && ! fs.isDirectory(fs.join(systemAppPath, appName))) { - return; - } - - try { - if (! mount) { - mount = '/_system/' + appName; - } - - var found = aal.firstExample({ type: "mount", mount: mount }); - - if (found === null) { - var opts = {reload: false}; - var prefix = systemCollectionPrefix(appName); - if (prefix) { - opts.collectionPrefix = prefix; - } - exports.mount(appName, mount, opts); - - var doc = mountFromId(mount); - var app = createApp(doc.app); - - try { - setupApp(app, mount, doc.options.collectionPrefix); - } catch(err) { - console.errorLines( - "Setup of System App '%s' failed: %s", appName, String(err.stack || err)); - return; - } - } - } - catch (err) { - console.error("unable to mount system application '%s': %s", appName, String(err)); - } - }); - } -}; -*/ - -(function() { - "use strict"; -// ----------------------------------------------------------------------------- -// --CHAPTER-- used code -// ----------------------------------------------------------------------------- - -// ----------------------------------------------------------------------------- -// --SECTION-- imports -// ----------------------------------------------------------------------------- - - var fs = require("fs"); - var utils = require("org/arangodb/foxx/manager-utils"); - var store = require("org/arangodb/foxx/store"); - var ArangoApp = require("org/arangodb/foxx/arangoApp").ArangoApp; - var arangodb = require("org/arangodb"); - var ArangoError = arangodb.ArangoError; - var checkParameter = arangodb.checkParameter; - var errors = arangodb.errors; - var download = require("internal").download; - - var throwDownloadError = arangodb.throwDownloadError; - var throwFileNotFound = arangodb.throwFileNotFound; - -// ----------------------------------------------------------------------------- -// --SECTION-- private variables -// ----------------------------------------------------------------------------- - -var appCache = {}; - -// ----------------------------------------------------------------------------- -// --SECTION-- private functions -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief lookup app in cache -/// Returns either the app or undefined if it is not cached. -//////////////////////////////////////////////////////////////////////////////// - -var lookupApp = function(mount) { - return appCache[mount]; -}; //////////////////////////////////////////////////////////////////////////////// /// @brief validates a manifest file and returns it. @@ -1313,36 +246,9 @@ var computeAppPath = function(mount) { /// @brief executes an app script //////////////////////////////////////////////////////////////////////////////// -var executeAppScript = function(app, name, mount, prefix) { +var executeAppScript = function(app, name) { var desc = app._manifest; - - if (! desc) { - throw new ArangoError({ - errorNum: errors.ERROR_INVALID_APPLICATION_MANIFEST.code, - errorMessage: "Invalid application manifest, app " + arangodb.inspect(app) - }); - } - - var root; - var devel = false; - root = computeRootAppPath(mount); - if (desc.hasOwnProperty(name)) { - var appContext = app.createAppContext(); - - appContext.mount = mount; - appContext.collectionPrefix = prefix; - appContext.options = app._options; - appContext.configuration = app._options.configuration; - appContext.basePath = fs.join(root, app._path); - appContext.baseUrl = '/_db/' + encodeURIComponent(arangodb.db._name()) + mount; - - appContext.isDevelopment = devel; - appContext.isProduction = ! devel; - appContext.manifest = app._manifest; - - extendContext(appContext, app, root); - app.loadAppScript(desc[name]); } }; @@ -1351,16 +257,16 @@ var executeAppScript = function(app, name, mount, prefix) { /// @brief sets up an app //////////////////////////////////////////////////////////////////////////////// - var setupApp = function (app, mount, prefix) { - return executeAppScript(app, "setup", mount, prefix); + var setupApp = function (app) { + return executeAppScript(app, "setup"); }; //////////////////////////////////////////////////////////////////////////////// /// @brief tears down an app //////////////////////////////////////////////////////////////////////////////// - var teardownApp = function (app, mount, prefix) { - return executeAppScript(app, "teardown", mount, prefix); + var teardownApp = function (app) { + return executeAppScript(app, "teardown"); }; //////////////////////////////////////////////////////////////////////////////// @@ -1374,7 +280,6 @@ var executeAppScript = function(app, name, mount, prefix) { var file = fs.join(root, path, "manifest.json"); var manifest = validateManifestFile(file); - var collectionPrefix = if (manifest === undefined) { //TODO Error Handeling @@ -1390,6 +295,7 @@ var executeAppScript = function(app, name, mount, prefix) { mount: mount, isSystem: isSystemMount(mount), isDevelopment: false + }; }; @@ -1575,7 +481,7 @@ var setup = function (mount) { var app = lookupApp(mount); try { - setupApp(app, mount, prefixFromMount(mount)); + setupApp(app); } catch (err) { console.errorLines( "Setup not possible for mount '%s': %s", mount, String(err.stack || err)); @@ -1601,7 +507,7 @@ var teardown = function (mount) { var app = lookupApp(mount); try { - teardownApp(app, mount, prefixFromMount(mount)); + teardownApp(app); } catch (err) { console.errorLines( "Teardown not possible for mount '%s': %s", mount, String(err.stack || err)); @@ -1618,153 +524,12 @@ var scanFoxx = function(mount, options) { delete appCache[mount]; var app = createApp(mount, options); utils.tmp_getStorage().save(app.toJSON()); + var routes = routeApp(app); + require("console").log("Routes", Object.keys(routes)); // TODO Routing? }; -//////////////////////////////////////////////////////////////////////////////// -/// @brief computes the routes of an app -//////////////////////////////////////////////////////////////////////////////// -function routingAalApp (app) { - "use strict"; - - var i; - - var prefix = app.collectionPrefix; // TODO - var defaultDocument = app._manifest.defaultDocument; // TODO by default "index.html" - - // setup the routes - var routes = { - urlPrefix: mount, - routes: [], - middleware: [], - context: {}, - models: {}, - - foxx: true, - - appContext: { - app: app, - module: app._module // TODO - } - }; - - var p = mount; - var devel = app._isDevelopment; - - if ((p + defaultDocument) !== p) { - // only add redirection if src and target are not the same - routes.routes.push({ - "url" : { match: "/" }, - "action" : { - "do" : "org/arangodb/actions/redirectRequest", - "options" : { - "permanently" : ! devel, - "destination" : defaultDocument, - "relative" : true - } - } - }); - } - - // mount all exports - if (app._manifest.hasOwnProperty("exports")) { - var exps = app._manifest.exports; - var result, context; - - for (i in exps) { - if (exps.hasOwnProperty(i)) { - file = exps[i]; - result = {}; - - // TODO ? - context = { exports: result }; - - // TODO - appContext = _.extend({}, appContextTempl); - appContext.prefix = "/"; - - app.loadAppScript(file, { context: context }); - - app._exports[i] = result; - } - } - } - - // mount all controllers - var controllers = app._manifest.controllers; - - try { - for (i in controllers) { - if (controllers.hasOwnProperty(i)) { - file = controllers[i]; - - // TODO ???? - // set up a context for the application start function - appContext = _.extend({}, appContextTempl); - appContext.prefix = arangodb.normalizeURL("/" + i); // app mount - appContext.routingInfo = {}; - appContext.foxxes = []; - - app.loadAppScript(file, { transform: transformScript(file) }); - - // ............................................................................. - // routingInfo - // ............................................................................. - - var foxxes = appContext.foxxes; - var u; - - for (u = 0; u < foxxes.length; ++u) { - var foxx = foxxes[u]; - var ri = foxx.routingInfo; - var rm = [ "routes", "middleware" ]; - - var route; - var j; - var k; - - _.extend(routes.models, foxx.models); - - p = ri.urlPrefix; - - for (k = 0; k < rm.length; ++k) { - var key = rm[k]; - - if (ri.hasOwnProperty(key)) { - var rt = ri[key]; - - for (j = 0; j < rt.length; ++j) { - route = rt[j]; - - if (route.hasOwnProperty("url")) { - route.url.match = arangodb.normalizeURL(p + "/" + route.url.match); - } - - route.context = i; - - routes[key].push(route); - } - } - } - } - } - } - - // install all files and assets - installAssets(app, routes); - - // and return all routes - return routes; - } - catch (err) { - console.errorLines( - "Cannot compute Foxx application routes: %s", String(err.stack || err)); - throw err; - } - - return null; -} //////////////////////////////////////////////////////////////////////////////// /// @brief Internal install function. Check install. @@ -1910,6 +675,10 @@ exports.search = store.search; exports.searchJson = store.searchJson; exports.update = store.update; exports.info = store.info; + +// TODO implement!! +exports.initializeFoxx = function () {}; + }()); // ----------------------------------------------------------------------------- diff --git a/js/server/modules/org/arangodb/foxx/routing.js b/js/server/modules/org/arangodb/foxx/routing.js index 040575e917..c49b891ed6 100644 --- a/js/server/modules/org/arangodb/foxx/routing.js +++ b/js/server/modules/org/arangodb/foxx/routing.js @@ -1,5 +1,5 @@ /*jshint strict: false */ -/*global module, require, exports */ +/*global require, exports */ //////////////////////////////////////////////////////////////////////////////// /// @brief Foxx routing @@ -27,381 +27,419 @@ /// @author Dr. Frank Celler /// @author Copyright 2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// +(function() { -// ----------------------------------------------------------------------------- -// --SECTION-- private variables -// ----------------------------------------------------------------------------- + // ----------------------------------------------------------------------------- + // --SECTION-- Imports + // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @brief development mounts -//////////////////////////////////////////////////////////////////////////////// + var arangodb = require("org/arangodb"); + var preprocess = require("org/arangodb/foxx/preprocessor").preprocess; + var _ = require("underscore"); + var fs = require("fs"); + var frontendDevelopmentMode = require("internal").frontendDevelopmentMode; + var console = require("console"); -var DEVELOPMENTMOUNTS = null; + // ----------------------------------------------------------------------------- + // --SECTION-- private functions + // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @brief mounted apps -//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /// @brief excludes certain files + //////////////////////////////////////////////////////////////////////////////// + + var excludeFile = function (name) { + var parts = name.split('/'); -var MOUNTED_APPS = {}; + if (parts.length > 0) { + var last = parts[parts.length - 1]; -// ----------------------------------------------------------------------------- -// --SECTION-- private functions -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief computes the routes of an app -//////////////////////////////////////////////////////////////////////////////// - -function routingAalApp (app, mount, options) { - "use strict"; - - MOUNTED_APPS[mount] = app; - - var i, prefix; - - if (mount === "") { - mount = "/"; - } - else { - mount = arangodb.normalizeURL(mount); - } - if (mount[0] !== "/") { - console.errorLines( - "Cannot mount Foxx application: '%s'. Mount '%s' has to be absolute", app._name, mount); - return; - } - - // compute the collection prefix - if (options.collectionPrefix === undefined) { - prefix = prefixFromMount(mount); - } - else { - prefix = options.collectionPrefix; - } - - var defaultDocument = "index.html"; - - if (app._manifest.hasOwnProperty("defaultDocument")) { - defaultDocument = app._manifest.defaultDocument; - } - - - // setup the routes - var routes = { - urlPrefix: mount, - routes: [], - middleware: [], - context: {}, - models: {}, - - foxx: true, - - appContext: { - name: app._name, // app name - version: app._version, // app version - appId: app._id, // app identifier - mount: mount, // global mount - options: options, // options - collectionPrefix: prefix // collection prefix + // exclude all files starting with . + if (last[0] === '.') { + return true; + } } + + return false; }; - var p = mount; + //////////////////////////////////////////////////////////////////////////////// + /// @brief builds one asset of an app + //////////////////////////////////////////////////////////////////////////////// - if (p !== "/") { - p = mount + "/"; - } + var buildAssetContent = function(app, assets, basePath) { + var i, j, m; - if ((p + defaultDocument) !== p) { - // only add redirection if src and target are not the same - routes.routes.push({ - "url" : { match: "/" }, - "action" : { - "do" : "org/arangodb/actions/redirectRequest", - "options" : { - "permanently" : (app._id.substr(0,4) !== 'dev'), - "destination" : defaultDocument, - "relative" : true + var reSub = /(.*)\/\*\*$/; + var reAll = /(.*)\/\*$/; + + var files = []; + + for (j = 0; j < assets.length; ++j) { + var asset = assets[j]; + var match = reSub.exec(asset); + + if (match !== null) { + m = fs.listTree(fs.join(basePath, match[1])); + + // files are sorted in file-system order. + // this makes the order non-portable + // we'll be sorting the files now using JS sort + // so the order is more consistent across multiple platforms + m.sort(); + + for (i = 0; i < m.length; ++i) { + var filename = fs.join(basePath, match[1], m[i]); + + if (! excludeFile(m[i])) { + if (fs.isFile(filename)) { + files.push(filename); + } + } } } - }); - } + else { + match = reAll.exec(asset); - // template for app context - var devel = false; - var root; - - if (app._manifest.isSystem) { - root = module.systemAppPath(); - } - else if (app._id.substr(0,4) === "dev:") { - devel = true; - root = module.devAppPath(); - } - else { - root = module.appPath(); - } - - var appContextTempl = app.createAppContext(); - - appContextTempl.mount = mount; // global mount - appContextTempl.options = options; - appContextTempl.configuration = app._options.configuration; - appContextTempl.collectionPrefix = prefix; // collection prefix - appContextTempl.basePath = fs.join(root, app._path); - appContextTempl.baseUrl = '/_db/' + encodeURIComponent(arangodb.db._name()) + mount; - - appContextTempl.isDevelopment = devel; - appContextTempl.isProduction = ! devel; - - appContextTempl.manifest = app._manifest; - extendContext(appContextTempl, app, root); - - var appContext; - var file; - - // mount all exports - if (app._manifest.hasOwnProperty("exports")) { - var exps = app._manifest.exports; - var result, context; - - for (i in exps) { - if (exps.hasOwnProperty(i)) { - file = exps[i]; - result = {}; - context = { exports: result }; - - appContext = _.extend({}, appContextTempl); - appContext.prefix = "/"; - - app.loadAppScript(appContext, file, { context: context }); - - app._exports[i] = result; - } - } - } - - // mount all controllers - var controllers = app._manifest.controllers; - - try { - for (i in controllers) { - if (controllers.hasOwnProperty(i)) { - file = controllers[i]; - - // set up a context for the application start function - appContext = _.extend({}, appContextTempl); - appContext.prefix = arangodb.normalizeURL("/" + i); // app mount - appContext.routingInfo = {}; - appContext.foxxes = []; - - app.loadAppScript(appContext, file, { transform: transformScript(file) }); - - // ............................................................................. - // routingInfo - // ............................................................................. - - var foxxes = appContext.foxxes; - var u; - - for (u = 0; u < foxxes.length; ++u) { - var foxx = foxxes[u]; - var ri = foxx.routingInfo; - var rm = [ "routes", "middleware" ]; - - var route; - var j; - var k; - - _.extend(routes.models, foxx.models); - - p = ri.urlPrefix; - - for (k = 0; k < rm.length; ++k) { - var key = rm[k]; - - if (ri.hasOwnProperty(key)) { - var rt = ri[key]; - - for (j = 0; j < rt.length; ++j) { - route = rt[j]; - - if (route.hasOwnProperty("url")) { - route.url.match = arangodb.normalizeURL(p + "/" + route.url.match); - } - - route.context = i; - - routes[key].push(route); - } - } + if (match !== null) { + throw new Error("Not implemented"); + } + else { + if (! excludeFile(asset)) { + files.push(fs.join(basePath, asset)); } } } } - // install all files and assets - installAssets(app, routes); + var content = ""; - // remember mount point - MOUNTED_APPS[mount] = app; - - // and return all routes - return routes; - } - catch (err) { - delete MOUNTED_APPS[mount]; - - console.errorLines( - "Cannot compute Foxx application routes: %s", String(err.stack || err)); - throw err; - } - - return null; -} - -// ----------------------------------------------------------------------------- -// --SECTION-- public functions -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns the app routes -//////////////////////////////////////////////////////////////////////////////// - -exports.appRoutes = function () { - "use strict"; - - var aal = getStorage(); - var find = aal.byExample({ type: "mount", active: true }); - - var routes = []; - // Variables needed in loop - var doc, appId, mount, options, app, r; - - while (find.hasNext()) { - doc = find.next(); - appId = doc.app; - mount = doc.mount; - options = doc.options || {}; - - app = createApp(appId, options); - if (app !== undefined) { + for (i = 0; i < files.length; ++i) { try { + var c = fs.read(files[i]); - r = routingAalApp(app, mount, options); - - if (r === null) { - throw new Error("Cannot compute the routing table for Foxx application '" - + app._id + "', check the log file for errors!"); - } - - routes.push(r); - - if (! developmentMode) { - console.debug("Mounted Foxx application '%s' on '%s'", appId, mount); - } + content += c + "\n"; } catch (err) { - console.error("Cannot mount Foxx application '%s': %s", appId, String(err.stack || err)); + console.error("Cannot read asset '%s'", files[i]); } } - } - return routes; -}; + return content; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief installs an asset for an app + //////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns the development routes -//////////////////////////////////////////////////////////////////////////////// + var buildFileAsset = function(app, path, basePath, asset) { + var content = buildAssetContent(app, asset.files, basePath); + var type; -exports.developmentRoutes = function () { - "use strict"; + // ............................................................................. + // content-type detection + // ............................................................................. - var mounts = []; - var routes = []; + // contentType explicitly specified for asset + if (asset.hasOwnProperty("contentType") && asset.contentType !== '') { + type = asset.contentType; + } - var root = module.devAppPath(); - var files = fs.list(root); - var j; + // path contains a dot, derive content type from path + else if (path.match(/\.[a-zA-Z0-9]+$/)) { + type = arangodb.guessContentType(path); + } - // Variables required in loop - var m, mf, appId, mount, options, app, r; + // path does not contain a dot, + // derive content type from included asset names + else if (asset.files.length > 0) { + type = arangodb.guessContentType(asset.files[0]); + } - for (j = 0; j < files.length; ++j) { - m = fs.join(root, files[j], "manifest.json"); - mf = validateManifestFile(m); - if (mf !== undefined) { + // use built-in defaulti content-type + else { + type = arangodb.guessContentType(""); + } - appId = "dev:" + mf.name + ":" + files[j]; - mount = "/dev/" + files[j]; - options = { - collectionPrefix : prefixFromMount(mount) + // ............................................................................. + // return content + // ............................................................................. + + return { contentType: type, body: content }; + }; + + //////////////////////////////////////////////////////////////////////////////// + /// @brief generates development asset action + //////////////////////////////////////////////////////////////////////////////// + + var buildDevelopmentAssetRoute = function(app, path, basePath, asset) { + return { + url: { match: path }, + action: { + callback: function (req, res) { + var c = buildFileAsset(app, path, basePath, asset); + + res.contentType = c.contentType; + res.body = c.body; + } + } + }; + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// @brief generates asset action + //////////////////////////////////////////////////////////////////////////////// + + var buildAssetRoute = function (app, path, basePath, asset) { + var c = buildFileAsset(app, path, basePath, asset); + + return { + url: { match: path }, + content: { contentType: c.contentType, body: c.body } + }; + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// @brief installs the assets of an app + //////////////////////////////////////////////////////////////////////////////// + + var installAssets = function (app, routes) { + var path; + + var desc = app._manifest; + + if (! desc) { + throw new Error("Invalid application manifest"); + } + + var normalized; + var route; + + if (desc.hasOwnProperty('assets')) { + for (path in desc.assets) { + if (desc.assets.hasOwnProperty(path)) { + var asset = desc.assets[path]; + var basePath = fs.join(app._root, app._path); + + if (asset.hasOwnProperty('basePath')) { + basePath = asset.basePath; + } + + normalized = arangodb.normalizeURL("/" + path); + + if (asset.hasOwnProperty('files')) { + if (frontendDevelopmentMode) { + route = buildDevelopmentAssetRoute(app, normalized, basePath, asset); + } + else { + route = buildAssetRoute(app, normalized, basePath, asset); + } + + routes.routes.push(route); + } + } + } + } + + if (desc.hasOwnProperty('files')) { + for (path in desc.files) { + if (desc.files.hasOwnProperty(path)) { + var directory = desc.files[path]; + + normalized = arangodb.normalizeURL("/" + path); + + route = { + url: { match: normalized + "/*" }, + action: { + "do": "org/arangodb/actions/pathHandler", + "options": { + root: app._root, + path: fs.join(app._path, directory) + } + } + }; + + routes.routes.push(route); + } + } + } + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// @brief returns the transform script + //////////////////////////////////////////////////////////////////////////////// + + function transformScript (file) { + "use strict"; + + if (/\.coffee$/.test(file)) { + return function (content) { + return preprocess(content, "coffee"); }; - app = createApp(appId, options, mf.name, m); - if (app !== undefined) { - try { - setupApp(app, mount, options.collectionPrefix); - } catch (err) { - console.errorLines( - "Setup of App '%s' with manifest '%s' failed: %s", mf.name, m, String(err)); - continue; - } + } - try { - r = routingAalApp(app, mount, options); - } catch (err) { - console.errorLines( - "Unable to properly route the App '%s': %s", mf.name, String(err.stack || err) - ); - continue; + return preprocess; + } + + + // ----------------------------------------------------------------------------- + // --SECTION-- public functions + // ----------------------------------------------------------------------------- + + //////////////////////////////////////////////////////////////////////////////// + /// @brief computes the routes of an app + //////////////////////////////////////////////////////////////////////////////// + + var routeApp = function (app) { + var i; + var mount = app._mount; + + var defaultDocument = app._manifest.defaultDocument; // TODO by default "index.html" + + // setup the routes + var routes = { + urlPrefix: mount, + routes: [], + middleware: [], + context: {}, + models: {}, + + foxx: true, + + appContext: { + app: app, + module: app._module // TODO + } + }; + + var p = mount; + var devel = app._isDevelopment; + + if ((p + defaultDocument) !== p) { + // only add redirection if src and target are not the same + routes.routes.push({ + "url" : { match: "/" }, + "action" : { + "do" : "org/arangodb/actions/redirectRequest", + "options" : { + "permanently" : ! devel, + "destination" : defaultDocument, + "relative" : true + } } - if (r === null) { - console.errorLines("Cannot compute the routing table for Foxx application '%s'" , app._id); - continue; + }); + } + + var tmpContext, file; + var result, context; + // mount all exports + if (app._manifest.hasOwnProperty("exports")) { + var exps = app._manifest.exports; + + for (i in exps) { + if (exps.hasOwnProperty(i)) { + file = exps[i]; + result = {}; + + // TODO ? + context = { exports: result }; + + tmpContext = {prefix: "/"}; + + app.loadAppScript(file, { context: context, appContext: tmpContext }); + + app._exports[i] = result; } - routes.push(r); - var desc = { - _id: "dev/" + app._id, - _key: app._id, - type: "mount", - app: app._id, - name: app._name, - description: app._manifest.description, - repository: app._manifest.repository, - license: app._manifest.license, - author: app._manifest.author, - mount: mount, - active: true, - collectionPrefix: options.collectionPrefix, - isSystem: app._manifest.isSystem || false, - options: options - }; - mounts.push(desc); } } - } - DEVELOPMENTMOUNTS = mounts; + // mount all controllers + var controllers = app._manifest.controllers; - return routes; -}; + try { + for (i in controllers) { + if (controllers.hasOwnProperty(i)) { + file = controllers[i]; -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns the development mounts -/// -/// Must be called after developmentRoutes. -//////////////////////////////////////////////////////////////////////////////// + // TODO ???? + // set up a context for the application start function + tmpContext = { + prefix: arangodb.normalizeURL("/" + i), // app mount + routingInfo: {}, + foxxes: [] + }; -exports.developmentMounts = function () { - "use strict"; + app.loadAppScript(file, { + transform: transformScript(file), + appContext: tmpContext + }); - if (DEVELOPMENTMOUNTS === null) { - exports.developmentRoutes(); - } + // ............................................................................. + // routingInfo + // ............................................................................. - return DEVELOPMENTMOUNTS; -}; + var foxxes = tmpContext.foxxes; + var u; + for (u = 0; u < foxxes.length; ++u) { + var foxx = foxxes[u]; + var ri = foxx.routingInfo; + var rm = [ "routes", "middleware" ]; + + var route; + var j; + var k; + + _.extend(routes.models, foxx.models); + + p = ri.urlPrefix; + + for (k = 0; k < rm.length; ++k) { + var key = rm[k]; + + if (ri.hasOwnProperty(key)) { + var rt = ri[key]; + + for (j = 0; j < rt.length; ++j) { + route = rt[j]; + + if (route.hasOwnProperty("url")) { + route.url.match = arangodb.normalizeURL(p + "/" + route.url.match); + } + + route.context = i; + + routes[key].push(route); + } + } + } + } + } + } + + // install all files and assets + installAssets(app, routes); + + // and return all routes + return routes; + } + catch (err) { + console.errorLines( + "Cannot compute Foxx application routes: %s", String(err.stack || err)); + throw err; + } + return null; + }; + + // ----------------------------------------------------------------------------- + // --SECTION-- Exports + // ----------------------------------------------------------------------------- + + exports.routeApp = routeApp; +}()); // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // -----------------------------------------------------------------------------