jive-sdk
Version:
Node.js SDK for Jive Software to assist with the development of add-ons
295 lines (253 loc) • 13.1 kB
JavaScript
/*
* 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 multer = require('multer');
var upload = multer({ dest: 'tmp-multer-uploads/' });
var express = require('express');
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Private
exports.legalRouteVerbs = ['get', 'put', 'delete', 'post' ];
exports.legalServiceFileExtensions = [ '.json', '.js' ];
function isValidFile(file ) {
return !(file.indexOf('.') == 0)
}
exports.recursiveDirectoryProcessor = function(app, definitionName, currentFsItem, root, processorFunction, filterFunction ) {
return q.nfcall( fs.stat, currentFsItem ).then( function(stat) {
if ( stat.isDirectory() ) {
return q.nfcall(fs.readdir, currentFsItem)
// process the routes
.then( function( subItems ) {
var promises = [];
subItems.forEach( function( subItem ) {
promises.push( exports.recursiveDirectoryProcessor( app, definitionName,
currentFsItem + '/' + subItem, root, processorFunction, filterFunction ) );
});
return q.all( promises );
});
} else if ( stat.isFile() ) {
if ( !filterFunction || filterFunction( currentFsItem ) ) {
var theDirectory = path.dirname( currentFsItem );
var theFile = path.basename( currentFsItem );
if ( isValidFile(theFile) ) {
var p = processorFunction( app, definitionName, theFile, theDirectory, root );
if ( p ) {
return p;
}
}
}
}
});
};
exports.setupRoutes = function(app, definitionName, routesPath, prefix) {
var processCandidateRoutes = function(app, definitionName, theFile, theDirectory, root ) {
var fileName = theFile.substring(0, theFile.length - 3);
var httpVerb = fileName.toLowerCase();
var routeHandlerPath = (theDirectory + '/' + fileName);
var _routeContextPath = ('/' + definitionName + theDirectory.replace(root,''));
var legalVerbFile = exports.legalRouteVerbs.indexOf(httpVerb) > -1;
var routeHandler = require(routeHandlerPath);
for ( var key in routeHandler ) {
if ( !routeHandler.hasOwnProperty(key) ) {
continue;
}
var routeContextPath = _routeContextPath;
var added = false;
var candidate = routeHandler[key];
if ( typeof candidate === 'function' && key === 'route' && legalVerbFile ) {
// if there is a function called 'route' and this is a legal verb file, register it
if ( prefix ) {
routeContextPath = prefix + routeContextPath;
}
app[httpVerb](routeContextPath, upload.any(), routeHandler.route.bind(app));
added = true;
// autowired verb file routes are never locked; explicitly wire if
// lock is wanted
} else {
// if its a valid route descriptor object, analyze it
if ( typeof candidate == 'object' && candidate['verb'] && candidate['route'] ) {
// its a valid handler
var path = candidate['path'] || key;
if ( path !== '/' ) {
if ( path.indexOf('/') === 0 ) {
// in this case of /something
// its an absolute route ... use that as the mapping
routeContextPath = path;
} else {
routeContextPath = (prefix ? prefix : '') + routeContextPath + "/" + path;
}
}
httpVerb = candidate['verb'] || [ 'get' ]; // default to GET verb
if (!Array.isArray(httpVerb)) {
httpVerb = [ httpVerb];
} // end if
httpVerb.forEach(
function(verb) {
verb = verb.toLowerCase();
app[verb](routeContextPath, upload.any(), candidate['route']);
// lock the route if its marked to be locked
if ( candidate['jiveLocked'] ) {
service.security().lockRoute({
'verb' : verb,
'path' : routeContextPath
});
jive.logger.info("locked route:", verb, routeContextPath);
} // end if
} // end function
); // end forEach
/*** CONVERTING TO UPPER CASE FOR DISPLAY ***/
httpVerb = httpVerb.map(function(x){ return x.toUpperCase() });
added = true;
}
}
if ( added ) {
jive.logger.debug('Route added for', definitionName, ':',
Array.isArray(httpVerb) ? httpVerb : '['+httpVerb.toUpperCase()+']',routeContextPath, ' -> ',
routeHandlerPath + ".js" );
}
}
};
return exports.recursiveDirectoryProcessor( app, definitionName, routesPath, routesPath, processCandidateRoutes );
};
function defaultSetupEventListener(handlerInfo, 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'], handlerInfo['handler']);
} else {
jive.events.registerEventListener(handlerInfo['event'], handlerInfo['handler'], {
'description' : handlerInfo['description'] || 'Unique to definition',
'eventListener' : eventListener
});
}
}
exports.setupServices = function( app, definitionName, svcDir, setupEventListener, setupContext ) {
/////////////////////////////////////////////////////
// apply definition specific tasks, life cycle events, etc.
setupEventListener = setupEventListener || defaultSetupEventListener;
return exports.recursiveDirectoryProcessor( app, definitionName, svcDir, svcDir,
function(app, definitionName, theFile, theDirectory) {
var taskPath = theDirectory + '/' + theFile;
var target = require(taskPath);
if ( jive.events.globalEvents.indexOf(definitionName) != -1
|| jive.events.pushQueueEvents.indexOf(definitionName) != -1 ) {
throw new Error('Illegal definition name ' + definitionName + ', collides with a reserved system identifier.' +
'Please choose a different definition name.');
}
// do service bootstrap
if ( target['onBootstrap'] ) {
jive.events.addLocalEventListener( "serviceBootstrapped", function() {
// bootstrap
jive.logger.debug("Bootstrapping " + definitionName + "...");
try {
var returned = target['onBootstrap'](app);
if ( returned ) {
// any error here precipitates
returned.catch( function(e) {
jive.logger.fatal(e.stack);
process.exit(-1);
});
}
} catch(e) {
jive.logger.fatal(e.stack);
process.exit(-1);
}
});
}
// event handlers
target.eventHandlers = target.eventHandlers || [];
target.eventHandlers.forEach(function (handlerInfo) {
setupEventListener(handlerInfo, definitionName, setupContext);
});
// recurrent tasks
// these are scheduled only if they haven't yet been scheduled by some other node
var tasks = target.task;
if ( ( service.role.isWorker() || service.role.isPusher() ) && tasks) {
var tasksToAdd = [];
if (tasks['forEach']) {
tasks.forEach(function(t) {
tasksToAdd.push(t);
});
} else {
// if the task provided is just a function, then convert to object with 60 second interval
tasksToAdd.push(typeof tasks === 'function' ? { 'handler': tasks, 'interval': 60 * 1000 } : tasks);
}
var noIDCounter = {};
tasksToAdd.forEach(function(task) {
var eventID = task['event'], handler = task['handler'], interval = task['interval'] || 60 * 1000,
context = task['context'] || {}, timeout = task['timeout'], event = task['event'];
if ( event && handler ) {
// anomalous situation; tasks should not have an inline handler AND a named event (which
// is a pointer to a handler).
// in this case, then inline handler wins; remove the named event.
jive.logger.warn('Task for', definitionName,'defined both inline handler and event; ' +
'honoring only the inline handler.');
event = undefined;
}
if ( !eventID ) {
// if no eventID -- then the event is <tilename>.<interval>
var eventIDBase = definitionName + ( interval ? '.' + interval : '' );
var eventIDCount = noIDCounter[eventIDBase];
if ( !eventIDCount ) {
eventIDCount = 0;
}
eventID = eventIDBase + '.' + eventIDCount;
noIDCounter[eventIDBase] = ++eventIDCount;
}
if ( !handler ) {
// task did not come with an inline handler;
// try to resolve one from listeners on the named event for the task
handler = jive.events.getEventListeners(event, definitionName );
if ( handler && handler['forEach'] ) {
handler = handler[0]; // get only the first one
}
if ( !handler ) {
throw new Error('Task for tile definition "'
+ definitionName + '" must specify a function handler, or reference an event (which has a handler).');
}
} else {
// task did come with an inline handler;
// mix it into the list of target eventHandlers
setupEventListener( {
'event' : eventID,
'handler' : handler
}, definitionName );
target.eventHandlers.push( );
}
context['event'] = eventID;
context['eventListener'] = definitionName;
// only attempt to schedule events after bootstrap is complete
jive.events.addLocalEventListener( "serviceBootstrapped", function() {
jive.context.scheduler.schedule(eventID, context, interval, undefined, undefined, timeout );
});
});
}
},
function(currentFsItem) {
return exports.legalServiceFileExtensions.indexOf(path.extname( currentFsItem ) ) > -1;
}
);
};