UNPKG

jive-sdk

Version:

Node.js SDK for Jive Software to assist with the development of add-ons

301 lines (263 loc) 11.2 kB
/* * Copyright 2013 Jive Software * * 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. */ var fs = require('fs'), q = require('q'), path = require('path'), jive = require('../api'), service = require('./service'); var express = require('express'); var consolidate = require('consolidate'); var mustache = consolidate.mustache; var baseSetup = require('./baseSetup'); var definitionSetup = Object.create(baseSetup); module.exports = definitionSetup; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Private function isValidFile(file ) { return !(file.indexOf('.') == 0) } function fsexists(path) { var deferred = q.defer(); var method = fs.exists ? fs.exists : require('path').exists; method( path, function(exists ) { deferred.resolve(exists); }); return deferred.promise; } var tileLifeCycleEvents = [ jive.constants.globalEventNames.NEW_INSTANCE, jive.constants.globalEventNames.INSTANCE_UPDATED, jive.constants.globalEventNames.INSTANCE_REMOVED ]; var tileDataPushEvents = [ jive.constants.globalEventNames.DATA_PUSHED, jive.constants.globalEventNames.ACTIVITY_PUSHED, jive.constants.globalEventNames.COMMENT_PUSHED ]; function buildHandlerFunction(handlerInfo) { if ( handlerInfo['globalListener'] ) { // if explicitly marked as a globalListener, then there are no conditions for handling, just pass it to the // event handler return handlerInfo['handler']; } else { // otherwise its a 'protected' handler return function(context, event ) { var definitionName = handlerInfo['definitionName']; var eventListener; if ( context['eventListener'] ) { eventListener = context['eventListener']; } else if ( event ) { if ( tileLifeCycleEvents.indexOf(event) > -1 ) { eventListener = context['name']; } if ( tileDataPushEvents.indexOf(event) > -1 ) { if ( context['theInstance'] ) { eventListener = context['theInstance']['name']; } } } if ( !eventListener || eventListener === definitionName ) { return handlerInfo['handler'](context); } } } } function tileDefinitionStupEventListener(handlerInfo, _definitionName, setupContext) { var definitionJson; if ( setupContext ) { definitionJson = setupContext['definitionJson']; } var definitionName = definitionJson ? definitionJson['name'] : _definitionName; handlerInfo['definitionName'] = definitionName; if (!handlerInfo['event']) { throw new Error('Event handler "' + definitionName + '" must specify an event name.'); } if (!handlerInfo['handler']) { throw new Error('Event handler "' + definitionName + '" must specify a function handler.'); } var eventListener = handlerInfo['eventListener'] || definitionName; if (jive.events.globalEvents.indexOf(handlerInfo['event']) != -1) { jive.events.registerEventListener(handlerInfo['event'], buildHandlerFunction(handlerInfo) ); } else { jive.events.registerEventListener( handlerInfo['event'], buildHandlerFunction(handlerInfo), { 'eventListener' : eventListener, 'description' : handlerInfo['description'] || 'Unique to definition' }); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // public /** * Returns a promise when defintion tasks, life cycle events, * and other things in the service directory have been processed. * @param definitionName * @param svcDir * @return {*} */ definitionSetup.setupDefinitionServices = function( app, definitionName, svcDir, definitionJson ) { ///////////////////////////////////////////////////// // apply definition specific tasks, life cycle events, etc. var next; if ( !definitionJson ) { // set it up var definitionLocation = require('path').dirname( svcDir ) + '/definition.json'; next = definitionSetup.setupDefinitionMetadata(definitionLocation); } else { next = q.resolve(definitionJson); } var setupContext = { 'definitionJson' : definitionJson }; return next.then( function(definition) { return definitionSetup.setupServices(app, definitionName, svcDir, tileDefinitionStupEventListener, setupContext ); }); }; /** * Returns a promise when all the routes have been calculated for a particular definition directory. */ definitionSetup.setupDefinitionRoutes = function(app, definitionName, routesPath){ return definitionSetup.setupRoutes( app, definitionName, routesPath ); }; definitionSetup.setupDefinitionMetadata = function(definitionPath) { // if a definition exists, read it from disk and save it return fsexists(definitionPath).then( function(exists) { if ( exists ) { return q.nfcall( fs.readFile, definitionPath).then( function(data ) { var definitionDirName = path.basename( path.dirname( definitionPath ) ); var definition = JSON.parse(data); definition.id = definition.id === '{{{definition_id}}}' ? null : definition.id; definition['definitionDirName'] = definitionDirName; return definition; }).then( function( definition ) { var apiToUse = definition['style'] === 'ACTIVITY' ? jive.extstreams.definitions : jive.tiles.definitions; return apiToUse.save(definition); }); } }); }; /** * Returns a promise for detecting when the definition directory has been autowired. * Assumes that the definintion directory is exactly the same as the definition name, eg.: * * /tiles/my-twitter-definition * * In this case the definition name is my-twitter-definition. * @param definitionDir */ definitionSetup.setupOneDefinition = function( app, definitionDir, definitionName ) { return q.nfcall( fs.stat, definitionDir ).then( function( stat ) { if ( stat.isDirectory() ) { definitionName = definitionName || (definitionDir.substring( definitionDir.lastIndexOf('/') + 1, definitionDir.length ) ); /// xxx todo this might not always work! use path var definitionPath = definitionDir + '/definition.json'; var routesPath = definitionDir + '/backend/routes'; var servicesPath = definitionDir + '/backend'; app.use( '/' + definitionName, express.static( definitionDir + '/public' ) ); // setup tile public directory var definitionApp = express(); definitionApp.engine('html', mustache); definitionApp.set('view engine', 'html'); definitionApp.set('views', definitionDir + '/public'); app.use( definitionApp ); // if a definition exists, read it from disk and save it var definitionPromise = definitionSetup.setupDefinitionMetadata(definitionPath); // wire up service and routes return definitionPromise.then( function(definitionJson) { var promises = []; promises.push( fsexists(routesPath).then( function(exists) { if ( exists ) { return definitionSetup.setupDefinitionRoutes( definitionApp, definitionName, routesPath ); } })); promises.push( fsexists(definitionDir).then( function(exists) { if ( exists ) { return definitionSetup.setupDefinitionServices( app, definitionName, servicesPath, definitionJson ); } })); return q.all(promises); }); } else { return q.resolve(); } }).catch( function(e) { jive.logger.error("Failed to setup tile at " + definitionName + "; " + e.stack); process.exit(-1); }); }; /** * Autowires each definition discovered in the provided definitions directory. * @param definitionsRootDir * @return {*} */ definitionSetup.setupAllDefinitions = function( app, definitionsRootDir ) { return jive.util.fsexists( definitionsRootDir).then( function(exists) { if ( exists ) { return q.nfcall(fs.readdir, definitionsRootDir).then(function(dirContents){ return setupDefinitionsSequentially(app,definitionsRootDir, dirContents); }); } else { return q.resolve(); } }).then( function () { // make sure that all definitions in the db actually still exist var remove = function(libraryToUse) { return libraryToUse.findAll().then( function (allDefinitions) { var proms = []; allDefinitions.forEach(function (tile) { var definitionDir = tile['definitionDirName']; if ( definitionDir ) { proms.push( jive.util.fsexists( definitionsRootDir + '/' + definitionDir).then( function(exists) { return !exists ? libraryToUse.remove( tile['id']) : q.resolve(); }) ); } return q.resolve(); }); return q.all(proms); }); }; return remove( jive.tiles.definitions).then( function() { return remove( jive.extstreams.definitions ); }); }); }; /** * This will setup tiledefinition persistence by having one tile insert at a time. * Tested on jive-persistence-postgres and jive-persistence-mongo * @param app * @param definitionsRootDir * @param directoryContents * @returns {*} */ function setupDefinitionsSequentially(app,definitionsRootDir, directoryContents) { if (directoryContents.length == 0) { return q.resolve(); } else { var item = directoryContents.shift(); if (!isValidFile(item)) { return setupDefinitionsSequentially(app,definitionsRootDir, directoryContents); } var dirPath = definitionsRootDir + '/' + item; return definitionSetup.setupOneDefinition(app, dirPath) .then(function () { return setupDefinitionsSequentially(app,definitionsRootDir, directoryContents); }); } }