mirror of https://gitee.com/bigwinds/arangodb
1784 lines
48 KiB
JavaScript
1784 lines
48 KiB
JavaScript
/*jslint indent: 2, nomen: true, maxlen: 120, sloppy: true, vars: true, white: true, regexp: true, plusplus: true, continue: true */
|
||
/*global module, require, exports */
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief Foxx application manager
|
||
///
|
||
/// @file
|
||
///
|
||
/// DISCLAIMER
|
||
///
|
||
/// Copyright 2013 triagens GmbH, Cologne, Germany
|
||
///
|
||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||
/// you may not use this file except in compliance with the License.
|
||
/// You may obtain a copy of the License at
|
||
///
|
||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||
///
|
||
/// Unless required by applicable law or agreed to in writing, software
|
||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
/// See the License for the specific language governing permissions and
|
||
/// limitations under the License.
|
||
///
|
||
/// Copyright holder is triAGENS GmbH, Cologne, Germany
|
||
///
|
||
/// @author Dr. Frank Celler
|
||
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
var arangodb = require("org/arangodb");
|
||
var ArangoError = arangodb.ArangoError;
|
||
|
||
var console = require("console");
|
||
var fs = require("fs");
|
||
var utils = require("org/arangodb/foxx/manager-utils");
|
||
|
||
var _ = require("underscore");
|
||
|
||
var executeGlobalContextFunction = require("internal").executeGlobalContextFunction;
|
||
var frontendDevelopmentMode = require("internal").frontendDevelopmentMode;
|
||
var checkParameter = arangodb.checkParameter;
|
||
var preprocess = require("org/arangodb/foxx/preprocessor").preprocess;
|
||
|
||
var developmentMode = require("internal").developmentMode;
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// --SECTION-- private variables
|
||
// -----------------------------------------------------------------------------
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief development mounts
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
var DEVELOPMENTMOUNTS = null;
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief mounted apps
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
var MOUNTED_APPS = {};
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// --SECTION-- private functions
|
||
// -----------------------------------------------------------------------------
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief returns the transform script
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function transformScript (file) {
|
||
'use strict';
|
||
|
||
if (/\.coffee$/.test(file)) {
|
||
return function (content) {
|
||
return preprocess(content, "coffee");
|
||
};
|
||
}
|
||
|
||
return preprocess;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief returns the aal collection
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function getStorage () {
|
||
'use strict';
|
||
|
||
return arangodb.db._collection('_aal');
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief check a manifest for completeness
|
||
///
|
||
/// this implements issue #590: Manifest Lint
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function checkManifest (filename, mf) {
|
||
'use strict';
|
||
|
||
// add some default attributes
|
||
if (! mf.hasOwnProperty("author")) {
|
||
// add a default (empty) author
|
||
mf.author = "";
|
||
}
|
||
|
||
if (! mf.hasOwnProperty("description")) {
|
||
// add a default (empty) description
|
||
mf.description = "";
|
||
}
|
||
|
||
// Validate all attributes specified in the manifest
|
||
// the following attributes are allowed with these types...
|
||
var expected = {
|
||
"assets": [ false, "object" ],
|
||
"author": [ false, "string" ],
|
||
"configuration": [ false, "object" ],
|
||
"contributors": [ false, "array" ],
|
||
"controllers": [ false, "object" ],
|
||
"defaultDocument": [ false, "string" ],
|
||
"description": [ true, "string" ],
|
||
"engines": [ false, "object" ],
|
||
"files": [ false, "object" ],
|
||
"isSystem": [ false, "boolean" ],
|
||
"keywords": [ false, "array" ],
|
||
"lib": [ false, "string" ],
|
||
"license": [ false, "string" ],
|
||
"name": [ true, "string" ],
|
||
"repository": [ false, "object" ],
|
||
"setup": [ false, "string" ],
|
||
"teardown": [ false, "string" ],
|
||
"thumbnail": [ false, "string" ],
|
||
"version": [ true, "string" ],
|
||
"rootElement": [ false, "boolean" ],
|
||
"exports": [ false, "object" ]
|
||
};
|
||
|
||
var att, failed = false;
|
||
|
||
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]);
|
||
|
||
if (actualType !== expectedType) {
|
||
console.error("Manifest '%s' uses an invalid data type (%s) for %s attribute '%s'",
|
||
filename,
|
||
actualType,
|
||
expectedType,
|
||
att);
|
||
failed = true;
|
||
}
|
||
}
|
||
else {
|
||
// attribute not present in manifest
|
||
if (expected[att][0]) {
|
||
// required attribute
|
||
console.error("Manifest '%s' does not provide required attribute '%s'",
|
||
filename,
|
||
att);
|
||
|
||
failed = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (failed) {
|
||
throw new Error("Manifest '%s' is invalid/incompatible. Please check the error logs.");
|
||
}
|
||
|
||
// additionally check if there are superfluous attributes in the manifest
|
||
for (att in mf) {
|
||
if (mf.hasOwnProperty(att)) {
|
||
if (! expected.hasOwnProperty(att)) {
|
||
console.warn("Manifest '%s' contains an unknown attribute '%s'",
|
||
filename,
|
||
att);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief extend a context with some helper functions
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function extendContext (context, app, root) {
|
||
'use strict';
|
||
|
||
var cp = context.collectionPrefix;
|
||
var cname = "";
|
||
|
||
if (cp !== "") {
|
||
cname = cp + "_";
|
||
}
|
||
|
||
context.collectionName = function (name) {
|
||
var replaced = (cname + name).replace(/[^a-zA-Z0-9]/g, '_').replace(/(^_+|_+$)/g, '').substr(0, 64);
|
||
|
||
if (replaced.length === 0) {
|
||
throw new Error("Cannot derive collection name from '" + name + "'");
|
||
}
|
||
|
||
return replaced;
|
||
};
|
||
|
||
context.collection = function (name) {
|
||
return arangodb.db._collection(this.collectionName(name));
|
||
};
|
||
|
||
context.path = function (name) {
|
||
return fs.join(root, app._path, name);
|
||
};
|
||
|
||
context.comments = [];
|
||
|
||
context.comment = function (str) {
|
||
this.comments.push(str);
|
||
};
|
||
|
||
context.clearComments = function () {
|
||
this.comments = [];
|
||
};
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief converts the mount point into the default prefix
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function prefixFromMount (mount) {
|
||
'use strict';
|
||
|
||
return mount.substr(1).replace(/-/g, "_").replace(/\//g, "_");
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @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 creates app object from application identifier
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function appFromAppId (appId) {
|
||
'use strict';
|
||
|
||
var app = module.createApp(appId, {});
|
||
|
||
if (app === null) {
|
||
throw new Error("Cannot find application '" + appId + "'");
|
||
}
|
||
|
||
return app;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @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';
|
||
|
||
var internal = require("internal");
|
||
|
||
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 executes an app script
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function executeAppScript (app, name, mount, prefix) {
|
||
'use strict';
|
||
|
||
var desc = app._manifest;
|
||
|
||
if (! desc) {
|
||
throw new Error("Invalid application manifest, app " + arangodb.inspect(app));
|
||
}
|
||
|
||
var root;
|
||
var devel = false;
|
||
|
||
if (app._manifest.isSystem) {
|
||
root = module.systemAppPath();
|
||
}
|
||
else if (app._id.substr(0,4) === "app:") {
|
||
root = module.appPath();
|
||
}
|
||
else if (app._id.substr(0,4) === "dev:") {
|
||
root = module.devAppPath();
|
||
devel = true;
|
||
}
|
||
else {
|
||
throw new Error("Cannot extract root path for app '" + app._id + "', unknown type");
|
||
}
|
||
|
||
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.isDevelopment = devel;
|
||
appContext.isProduction = ! devel;
|
||
appContext.manifest = app._manifest;
|
||
|
||
extendContext(appContext, app, root);
|
||
|
||
app.loadAppScript(appContext, desc[name]);
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief sets up an app
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function setupApp (app, mount, prefix) {
|
||
'use strict';
|
||
|
||
return executeAppScript(app, "setup", mount, prefix);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief tears down an app
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function teardownApp (app, mount, prefix) {
|
||
'use strict';
|
||
|
||
return executeAppScript(app, "teardown", mount, prefix);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @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,
|
||
author: app._manifest.author,
|
||
mount: mount,
|
||
active: true,
|
||
error: false,
|
||
isSystem: app._manifest.isSystem || false,
|
||
options: options
|
||
};
|
||
|
||
return aal.save(desc);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief computes the routes of an app
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function routingAalApp (app, mount, options) {
|
||
'use strict';
|
||
|
||
MOUNTED_APPS[mount] = app;
|
||
|
||
try {
|
||
var i, prefix;
|
||
|
||
if (mount === "") {
|
||
mount = "/";
|
||
}
|
||
else {
|
||
mount = arangodb.normalizeURL(mount);
|
||
}
|
||
|
||
if (mount[0] !== "/") {
|
||
throw new Error("Mount point must be absolute");
|
||
}
|
||
|
||
// 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
|
||
}
|
||
};
|
||
|
||
var p = mount;
|
||
|
||
if (p !== "/") {
|
||
p = mount + "/";
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 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.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;
|
||
|
||
for (i in exps) {
|
||
if (exps.hasOwnProperty(i)) {
|
||
file = exps[i];
|
||
var result = {};
|
||
var 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;
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// install all files and assets
|
||
installAssets(app, routes);
|
||
|
||
// 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));
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief scans fetched Foxx applications
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
function scanDirectory (path) {
|
||
'use strict';
|
||
|
||
var j;
|
||
|
||
if (typeof 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
|
||
|
||
|
||
for (j = 0; j < files.length; ++j) {
|
||
var m = fs.join(path, files[j], "manifest.json");
|
||
|
||
if (fs.exists(m)) {
|
||
try {
|
||
var thumbnail;
|
||
|
||
thumbnail = undefined;
|
||
var mf = JSON.parse(fs.read(m));
|
||
|
||
checkManifest(m, mf);
|
||
|
||
if (mf.hasOwnProperty('thumbnail') && mf.thumbnail !== null && mf.thumbnail !== '') {
|
||
var 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;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// --SECTION-- public functions
|
||
// -----------------------------------------------------------------------------
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief scans fetched Foxx applications
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.scanAppDirectory = function () {
|
||
'use strict';
|
||
|
||
var aal = getStorage();
|
||
|
||
// 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(<appId>, <mount>, [<options>])",
|
||
[ [ "Application identifier", "string" ],
|
||
[ "Mount path", "string" ] ],
|
||
[ appId, mount ] );
|
||
|
||
// .............................................................................
|
||
// mark than 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 = module.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 sets up a Foxx application
|
||
///
|
||
/// Input:
|
||
/// * mount: the mount identifier or path
|
||
///
|
||
/// Output:
|
||
/// -
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.setup = function (mount) {
|
||
'use strict';
|
||
|
||
checkParameter(
|
||
"setup(<mount>)",
|
||
[ [ "Mount identifier", "string" ] ],
|
||
[ mount ] );
|
||
|
||
var doc = mountFromId(mount);
|
||
var app = appFromAppId(doc.app);
|
||
|
||
setupApp(app, mount, doc.options.collectionPrefix);
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief tears down a Foxx application
|
||
///
|
||
/// Input:
|
||
/// * mount: the mount path starting with a "/"
|
||
///
|
||
/// Output:
|
||
/// -
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.teardown = function (mount) {
|
||
'use strict';
|
||
|
||
checkParameter(
|
||
"teardown(<mount>)",
|
||
[ [ "Mount identifier", "string" ] ],
|
||
[ mount ] );
|
||
|
||
var appId;
|
||
|
||
try {
|
||
var doc = mountFromId(mount);
|
||
|
||
appId = doc.app;
|
||
var app = appFromAppId(appId);
|
||
|
||
teardownApp(app, mount, doc.options.collectionPrefix);
|
||
}
|
||
catch (err) {
|
||
console.errorLines(
|
||
"Teardown not possible for mount '%s': %s", mount, String(err.stack || err));
|
||
}
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @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>)",
|
||
[ [ "Mount identifier", "string" ] ],
|
||
[ mount ] );
|
||
|
||
var doc = mountFromId(mount);
|
||
|
||
if (doc.isSystem) {
|
||
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>)",
|
||
[ [ "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(<mount>)",
|
||
[ [ "Application folder", "string" ] ],
|
||
[ filename ] );
|
||
|
||
var root = module.devAppPath();
|
||
var m = fs.join(root, filename, "manifest.json");
|
||
|
||
if (fs.exists(m)) {
|
||
try {
|
||
var mf = JSON.parse(fs.read(m));
|
||
|
||
checkManifest(m, mf);
|
||
|
||
var appId = "dev:" + mf.name + ":" + filename;
|
||
var mount = "/dev/" + filename;
|
||
var prefix = prefixFromMount(mount);
|
||
|
||
var app = module.createApp(appId, {});
|
||
|
||
if (app === null) {
|
||
throw new Error("Cannot find application '" + appId + "'");
|
||
}
|
||
|
||
setupApp(app, mount, prefix);
|
||
}
|
||
catch (err) {
|
||
throw new Error("Cannot read app manifest '" + m + "': " + String(err.stack || 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(<mount>)",
|
||
[ [ "Application folder", "string" ] ],
|
||
[ filename ] );
|
||
|
||
var root = module.devAppPath();
|
||
var m = fs.join(root, filename, "manifest.json");
|
||
|
||
if (fs.exists(m)) {
|
||
try {
|
||
var mf = JSON.parse(fs.read(m));
|
||
|
||
checkManifest(m, mf);
|
||
|
||
var appId = "dev:" + mf.name + ":" + filename;
|
||
var mount = "/dev/" + filename;
|
||
var prefix = prefixFromMount(mount);
|
||
|
||
var app = module.createApp(appId, {});
|
||
|
||
if (app === null) {
|
||
throw new Error("Cannot find application '" + appId + "'");
|
||
}
|
||
|
||
teardownApp(app, mount, prefix);
|
||
}
|
||
catch (err) {
|
||
throw new Error("Cannot read app manifest '" + m + "': " + String(err.stack || err));
|
||
}
|
||
}
|
||
else {
|
||
throw new Error("Cannot find manifest file '" + m + "'");
|
||
}
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief returns the app routes
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.appRoutes = function () {
|
||
'use strict';
|
||
|
||
var aal = getStorage();
|
||
|
||
return arangodb.db._executeTransaction({
|
||
collections: {
|
||
read: [ aal.name() ]
|
||
},
|
||
params: {
|
||
aal : aal
|
||
},
|
||
action: function (params) {
|
||
var find = params.aal.byExample({ type: "mount", active: true });
|
||
|
||
var routes = [];
|
||
|
||
while (find.hasNext()) {
|
||
var doc = find.next();
|
||
var appId = doc.app;
|
||
var mount = doc.mount;
|
||
var options = doc.options || { };
|
||
|
||
try {
|
||
var app = module.createApp(appId, options || {});
|
||
|
||
if (app === null) {
|
||
throw new Error("Cannot find application '" + appId + "'");
|
||
}
|
||
|
||
var 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);
|
||
}
|
||
}
|
||
catch (err) {
|
||
console.error("Cannot mount Foxx application '%s': %s", appId, String(err.stack || err));
|
||
}
|
||
}
|
||
|
||
return routes;
|
||
}
|
||
});
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief returns the development routes
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.developmentRoutes = function () {
|
||
'use strict';
|
||
|
||
var mounts = [];
|
||
var routes = [];
|
||
|
||
var root = module.devAppPath();
|
||
var files = fs.list(root);
|
||
var j;
|
||
|
||
for (j = 0; j < files.length; ++j) {
|
||
var m = fs.join(root, files[j], "manifest.json");
|
||
|
||
if (fs.exists(m)) {
|
||
try {
|
||
var mf = JSON.parse(fs.read(m));
|
||
|
||
checkManifest(m, mf);
|
||
|
||
var appId = "dev:" + mf.name + ":" + files[j];
|
||
var mount = "/dev/" + files[j];
|
||
var options = {
|
||
collectionPrefix : prefixFromMount(mount)
|
||
};
|
||
|
||
var app = module.createApp(appId, options);
|
||
|
||
if (app === null) {
|
||
throw new Error("Cannot find application '" + appId + "'");
|
||
}
|
||
|
||
setupApp(app, mount, options.collectionPrefix);
|
||
|
||
var 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);
|
||
|
||
var desc = {
|
||
_id: "dev/" + app._id,
|
||
_key: app._id,
|
||
type: "mount",
|
||
app: app._id,
|
||
name: app._name,
|
||
description: app._manifest.description,
|
||
author: app._manifest.author,
|
||
mount: mount,
|
||
active: true,
|
||
collectionPrefix: options.collectionPrefix,
|
||
isSystem: app._manifest.isSystem || false,
|
||
options: options
|
||
};
|
||
|
||
mounts.push(desc);
|
||
}
|
||
catch (err) {
|
||
console.errorLines(
|
||
"Cannot read app manifest '%s': %s", m, String(err.stack || err));
|
||
}
|
||
}
|
||
}
|
||
|
||
DEVELOPMENTMOUNTS = mounts;
|
||
|
||
return routes;
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief returns the development mounts
|
||
///
|
||
/// Must be called after developmentRoutes.
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.developmentMounts = function () {
|
||
'use strict';
|
||
|
||
if (DEVELOPMENTMOUNTS === null) {
|
||
exports.developmentRoutes();
|
||
}
|
||
|
||
return DEVELOPMENTMOUNTS;
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @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);
|
||
var err = new ArangoError();
|
||
err.errorNum = arangodb.errors.ERROR_APP_ALREADY_EXISTS.code;
|
||
err.errorMessage = arangodb.errors.ERROR_APP_ALREADY_EXISTS.message;
|
||
throw err;
|
||
}
|
||
|
||
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 returns all available FOXX applications
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.availableJson = function () {
|
||
'use strict';
|
||
|
||
return utils.availableJson();
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
///// @brief returns all installed FOXX applications
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.listJson = function () {
|
||
'use strict';
|
||
|
||
return utils.listJson();
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief returns the fishbowl collection
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.getFishbowlStorage = function () {
|
||
'use strict';
|
||
|
||
return utils.getFishbowlStorage();
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
/// @brief initializes the Foxx apps
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
exports.initializeFoxx = function () {
|
||
'use strict';
|
||
|
||
try {
|
||
exports.scanAppDirectory();
|
||
}
|
||
catch (err) {
|
||
console.error("cannot initialize Foxx application: %s", String(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) {
|
||
exports.mount(appName, mount, {reload: false});
|
||
|
||
var doc = mountFromId(mount);
|
||
var app = appFromAppId(doc.app);
|
||
|
||
setupApp(app, mount, doc.options.collectionPrefix);
|
||
}
|
||
}
|
||
catch (err) {
|
||
console.error("unable to mount system application '%s': %s", appName, String(err));
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// --SECTION-- END-OF-FILE
|
||
// -----------------------------------------------------------------------------
|
||
|
||
/// Local Variables:
|
||
/// mode: outline-minor
|
||
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
|
||
/// End:
|