UNPKG

webgme-engine

Version:

WebGME server and Client API without a GUI

466 lines (406 loc) 19.4 kB
/*globals define*/ /*eslint-env browser*/ /** * @author pmeijer / https://github.com/pmeijer */ define([ 'plugin/managerbase', 'blob/BlobClient', 'common/storage/project/project', 'common/Constants', 'common/util/key', 'q', 'superagent' ], function (PluginManagerBase, BlobClient, Project, CONSTANTS, generateKey, Q, superagent) { 'use strict'; var ROOT_PATH = ''; /** * * @param client * @param storage * @param state * @param mainLogger * @param gmeConfig * @constructor */ function PluginManager(client, storage, state, mainLogger, gmeConfig) { var self = this, logger = mainLogger.fork('PluginManager'), runningPlugins = {}; this.getCurrentPluginContext = function (pluginId, activeNodeId, activeSelectionIds) { var activeNode, validPlugins, context = { managerConfig: { project: client.getProjectObject(), branchName: client.getActiveBranchName(), commitHash: client.getActiveCommitHash(), activeNode: ROOT_PATH, activeSelection: [], namespace: '' }, pluginConfig: null }; // If executed from the Generic UI we can access the active- and selected-nodes. if (typeof WebGMEGlobal !== 'undefined') { /* eslint-disable no-undef*/ context.managerConfig.activeSelection = WebGMEGlobal.State.getActiveSelection(); context.managerConfig.activeNode = WebGMEGlobal.State.getActiveObject(); /* eslint-enable no-undef*/ } if (activeSelectionIds) { context.managerConfig.activeSelection = activeSelectionIds; } if (typeof activeNodeId === 'string') { context.managerConfig.activeNode = activeNodeId; } // Given the active-node we infer the namespace (user may still select another one). activeNodeId = context.managerConfig.activeNode; if (activeNodeId && pluginId) { activeNode = client.getNode(activeNodeId); if (activeNode === null) { logger.warn('Getting context for non-available' + ' node is dangerous and could lead to failed plugin execution [' + pluginId + '][' + activeNodeId + '].'); return context; } do { validPlugins = activeNode.getOwnRegistry('validPlugins'); if (validPlugins && validPlugins.indexOf(pluginId) > -1) { // The plugin was defined at this particular node, we use the namespace of it. context.managerConfig.namespace = activeNode.getNamespace(); break; } activeNode = activeNode.getBaseId() ? client.getNode(activeNode.getBaseId()) : null; } while (activeNode); } return context; }; function getPluginMetadata(pluginId) { var deferred = Q.defer(); superagent.get(gmeConfig.client.mountedPath + '/api/plugins/' + pluginId + '/metadata') .end(function (err, res) { if (err) { deferred.reject(err); } deferred.resolve(res.body); }); return deferred.promise; } function getSanitizedManagerConfig(config) { var sanitized = {}, keys = Object.keys(config); keys.forEach(function (key) { switch (key) { case 'project': if (typeof config.project === 'string') { sanitized.project = config.project; } else { sanitized.project = config.project.projectId; } break; default: sanitized[key] = config[key]; } }); return sanitized; } function getSanitizedPluginContext(context) { var sanitized = {}, keys = Object.keys(context); keys.forEach(function (key) { switch (key) { case 'managerConfig': sanitized.managerConfig = getSanitizedManagerConfig(context.managerConfig); break; default: sanitized[key] = context[key]; } }); return sanitized; } function getSanitizedPluginEntry(pluginEntry) { var sanitized = {}, keys = Object.keys(pluginEntry); keys.forEach(function (key) { switch (key) { case 'plugin': break; case 'context': sanitized.context = getSanitizedPluginContext(pluginEntry.context); break; default: sanitized[key] = pluginEntry[key]; } }); return sanitized; } /** * Run the plugin in the browser. * @param {string|function} pluginIdOrClass - id or class for plugin. * @param {object} context * @param {object} context.managerConfig - where the plugin should execute. * @param {ProjectInterface} context.managerConfig.project - project (e.g. client.getProjectObject()). * @param {string} [context.managerConfig.activeNode=''] - path to activeNode. * @param {string} [context.managerConfig.activeSelection=[]] - paths to selected nodes. * @param {string} context.managerConfig.commitHash - commit hash to start the plugin from. * @param {string} [context.managerConfig.branchName] - branch which to save to. * @param {string} [context.managerConfig.namespace=''] - used namespace ('' represents root namespace). * @param {object} [context.pluginConfig=%defaultForPlugin%] - specific configuration for the plugin. * @param {function(err, PluginResult)} callback */ this.runBrowserPlugin = function (pluginIdOrClass, context, callback) { var pluginEntry, blobClient = new BlobClient({ logger: logger.fork('BlobClient'), relativeUrl: gmeConfig.client.mountedPath + '/rest/blob/' }), pluginManager = new PluginManagerBase(blobClient, null, mainLogger, gmeConfig), plugin, executionId; pluginManager.browserSide = true; pluginManager.projectAccess = client.getProjectAccess(); pluginManager.notificationHandlers = [function (data, callback) { data.executionId = executionId; self.dispatchPluginNotification(data); callback(null); }]; // pluginManager.executePlugin(pluginIdOrClass, context.pluginConfig, context.managerConfig, callback); pluginManager.initializePlugin(pluginIdOrClass) .then(function (plugin_) { plugin = plugin_; pluginEntry = { id: plugin.getId(), name: plugin.getName(), plugin: plugin, metadata: plugin.pluginMetadata, context: context, canBeAborted: plugin.pluginMetadata.canBeAborted, start: Date.now(), clientSide: true, executionId: null, result: null }; executionId = generateKey({ id: pluginEntry.id, name: pluginEntry.name, start: pluginEntry.start }, gmeConfig); pluginEntry.executionId = executionId; runningPlugins[executionId] = pluginEntry; return pluginManager.configurePlugin(plugin, context.pluginConfig, context.managerConfig); }) .then(function () { return Q.ninvoke(pluginManager, 'runPluginMain', plugin); }) .then(function (result) { if (Object.hasOwn(runningPlugins, executionId)) { delete runningPlugins[executionId]; } else { logger.error('Running plugin registry misses entry [' + pluginEntry.id + '][' + executionId + '].'); } pluginEntry.result = result; client.dispatchEvent(client.CONSTANTS.PLUGIN_FINISHED, getSanitizedPluginEntry(pluginEntry)); callback(null, result); }) .catch(function (err) { if (Object.hasOwn(runningPlugins, executionId)) { delete runningPlugins[executionId]; } else { logger.error('Running plugin registry misses entry [' + pluginEntry.id + '][' + executionId + '].'); } pluginEntry.result = null; client.dispatchEvent(client.CONSTANTS.PLUGIN_FINISHED, getSanitizedPluginEntry(pluginEntry)); var pluginResult = pluginManager.getPluginErrorResult( plugin.getId(), plugin.getName(), 'Exception was raised, err: ' + err.stack, plugin && plugin.projectId ); logger.error(err.stack); callback(err.message, pluginResult); }) .done(); }; /** * Run the plugin on the server inside a worker process. * @param {string|function} pluginIdOrClass - id or class for plugin. * @param {object} context * @param {object} context.managerConfig - where the plugin should execute. * @param {ProjectInterface|string} context.managerConfig.project - project or id of project. * @param {string} [context.managerConfig.activeNode=''] - path to activeNode. * @param {string} [context.managerConfig.activeSelection=[]] - paths to selected nodes. * @param {string} context.managerConfig.commitHash - commit hash to start the plugin from. * @param {string} [context.managerConfig.branchName] - branch which to save to. * @param {string} [context.managerConfig.namespace=''] - used namespace ('' represents root namespace). * @param {object} [context.pluginConfig=%defaultForPlugin%] - specific configuration for the plugin. * @param {function} callback */ this.runServerPlugin = function (pluginIdOrClass, context, callback) { var pluginEntry, executionId, pluginId = typeof pluginIdOrClass === 'string' ? pluginIdOrClass : pluginIdOrClass.metadata.id; if (context.managerConfig.project instanceof Project) { context.managerConfig.project = context.managerConfig.project.projectId; } getPluginMetadata(pluginId) .then(function (metadata) { pluginEntry = { id: pluginId, name: metadata.name, plugin: null, metadata: metadata, context: context, canBeAborted: metadata.canBeAborted, start: Date.now(), clientSide: false, executionId: null, result: null }; executionId = generateKey({ id: pluginId, name: pluginEntry.name, start: pluginEntry.start }, gmeConfig); pluginEntry.executionId = executionId; runningPlugins[executionId] = pluginEntry; context.executionId = executionId; storage.simpleRequest({ command: CONSTANTS.SERVER_WORKER_REQUESTS.EXECUTE_PLUGIN, name: pluginId, context: context }, function (err, result) { if (Object.hasOwn(runningPlugins, executionId)) { delete runningPlugins[executionId]; } else { logger.error('Running plugin registry misses entry [' + pluginEntry.id + '][' + executionId + '].'); } pluginEntry.result = result; client.dispatchEvent(client.CONSTANTS.PLUGIN_FINISHED, getSanitizedPluginEntry(pluginEntry)); if (err) { callback(err, err.result); } else { callback(null, result); } }); }) .catch(function (err) { callback(err, null); }); }; /** * @param {string[]} pluginIds - All available plugins on the server. * @param {string} [nodePath=''] - Node to get the validPlugins from. * @returns {string[]} - Filtered plugin ids. */ this.filterPlugins = function (pluginIds, nodePath) { var filteredIds = [], validPlugins, i, node; logger.debug('filterPluginsBasedOnNode allPlugins, given nodePath', pluginIds, nodePath); if (!nodePath) { logger.debug('filterPluginsBasedOnNode nodePath not given - will fall back on root-node.'); nodePath = ROOT_PATH; } node = state.nodes[nodePath]; if (!node) { logger.warn('filterPluginsBasedOnNode node not loaded - will fall back on root-node.', nodePath); nodePath = ROOT_PATH; node = state.nodes[nodePath]; } if (!node) { logger.warn('filterPluginsBasedOnNode root node not loaded - will return full list.'); return pluginIds; } validPlugins = (state.core.getRegistry(node.node, 'validPlugins') || '').split(' '); for (i = 0; i < validPlugins.length; i += 1) { if (pluginIds.indexOf(validPlugins[i]) > -1) { filteredIds.push(validPlugins[i]); } else if (validPlugins[i] === '') { // Skip empty strings.. } else { logger.warn('Registered plugin for node at path "' + nodePath + '" is not amongst available plugins', pluginIds); } } return filteredIds; }; this.dispatchPluginNotification = function (data) { var notification = { severity: data.notification.severity || 'info', message: '[Plugin] ' + data.pluginName + ' - ' + data.notification.message }; if (typeof data.notification.progress === 'number') { notification.message += ' [' + data.notification.progress + '%]'; } logger.debug('plugin notification', data); if (data.notification && data.notification.type === CONSTANTS.STORAGE.PLUGIN_NOTIFICATION_TYPE.INITIATED) { if (Object.hasOwn(runningPlugins, data.executionId)) { runningPlugins[data.executionId].socketId = data.pluginSocketId; client.dispatchEvent(client.CONSTANTS.PLUGIN_INITIATED, getSanitizedPluginEntry(runningPlugins[data.executionId])); } } else { if (gmeConfig.plugin.suppressRegularNotifications !== true) { client.dispatchEvent(client.CONSTANTS.NOTIFICATION, notification); } client.dispatchEvent(client.CONSTANTS.PLUGIN_NOTIFICATION, data); } }; this.getRunningPlugins = function () { var sanitizedData = {}, executionIds = Object.keys(runningPlugins); executionIds.forEach(function (executionId) { if (Object.hasOwn(runningPlugins, executionId)) { sanitizedData[executionId] = getSanitizedPluginEntry(runningPlugins[executionId]); } }); return sanitizedData; }; this.abortPlugin = function (pluginExecutionId) { var pluginEntry = runningPlugins[pluginExecutionId]; if (pluginEntry) { if (!pluginEntry.metadata.canBeAborted) { throw new Error('Aborting plugin [' + pluginEntry.name + '] is not allowed.'); } if (pluginEntry.clientSide) { pluginEntry.plugin.onAbort(); } else if (pluginEntry.socketId) { storage.sendNotification({ type: CONSTANTS.STORAGE.PLUGIN_NOTIFICATION, notification: { toBranch: false, type: CONSTANTS.STORAGE.PLUGIN_NOTIFICATION_TYPE.ABORT, executionId: pluginExecutionId }, originalSocketId: pluginEntry.socketId, }); } } }; this.sendMessageToPlugin = function (pluginExecutionId, messageId, content) { var pluginEntry = runningPlugins[pluginExecutionId]; if (pluginEntry) { if (pluginEntry.clientSide) { pluginEntry.plugin.onMessage(messageId, content); } else if (pluginEntry.socketId) { storage.sendNotification({ type: CONSTANTS.STORAGE.PLUGIN_NOTIFICATION, notification: { toBranch: false, type: CONSTANTS.STORAGE.PLUGIN_NOTIFICATION_TYPE.MESSAGE, executionId: pluginExecutionId, messageId: messageId, content: content }, originalSocketId: pluginEntry.socketId, }); } } }; } return PluginManager; });